view release on metacpan or search on metacpan
- The Alzabo::Create::Schema->right_outer_join and
Alzabo::Create::Schema->left_outer_join methods have been removed.
Use the ->join method instead, which can now be used to do outer
joins as well, via:
$schema->join( join => [ left_outer_join => $foo, $bar ], ... )
- The functionality of Alzabo::Runtime::OuterJoinCursor has been
merged into Alzabo::Runtime::JoinCursor.
- Alzabo::Exception::Driver->bind now returns an array reference, not
an array.
BUG FIXES:
- Fix failure to load schema objects from file when $\ is set to
something like "\n". Reported by Brad Bowman.
- Fixed Postgres reverse engineering to work with slightly changed
system tables in 7.2.
- Bug fix related to MySQL auto_increment column and
Alzabo::Runtime::Table insert method. Basically, you couldn't
insert into a table and use its auto_increment feature.
- Alzabo::Table::set_prefetch now makes sure that primary key columns
are not included. It simply ignores them but they will not be
returned by the prefetch method.
- fix bug where some row retrieval methods would fail if not given a
'bind' parameter.
- Doc bug fix. Docs for Alzabo::Runtime::Table listed group_by_column
as simply group. Of course, this probably only needs to be used by
Alzabo::Runtime::Row anyway.
- Added Alzabo::Runtime::Table rows_where method.
- Added Alzabo::Runtime::Table all_rows method.
- Documented 'bind' parameter for Alzabo::Runtime::Table
rows_by_where_clause method.
---------------------------------------------------------------------------
0.07
- Fixed major bugs in Alzabo::Runtime::Table::insert method.
- Fixed bug in Alzabo::Runtime::Row::delete method related to API
change in 0.06
lib/Alzabo/Driver.pm view on Meta::CPAN
$self->_ensure_valid_dbh;
my %p = @_;
my $sth = $self->_prepare_and_execute(%p);
my @data;
eval
{
my @row;
$sth->bind_columns( \ (@row[ 0..$#{ $sth->{NAME_lc} } ] ) );
push @data, [@row] while $sth->fetch;
$sth->finish;
};
if ($@)
{
my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return wantarray ? @data : $data[0];
}
sub rows_hashref
{
my $self = shift;
my %p = @_;
$self->_ensure_valid_dbh;
my $sth = $self->_prepare_and_execute(%p);
my @data;
eval
{
my %hash;
$sth->bind_columns( \ ( @hash{ @{ $sth->{NAME_uc} } } ) );
push @data, {%hash} while $sth->fetch;
$sth->finish;
};
if ($@)
{
my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return @data;
}
sub one_row
{
my $self = shift;
my %p = @_;
lib/Alzabo/Driver.pm view on Meta::CPAN
my $sth = $self->_prepare_and_execute(%p);
my @row;
eval
{
@row = $sth->fetchrow_array;
$sth->finish;
};
if ($@)
{
my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return wantarray ? @row : $row[0];
}
sub one_row_hash
{
my $self = shift;
my %p = @_;
lib/Alzabo/Driver.pm view on Meta::CPAN
my %hash;
eval
{
my @row = $sth->fetchrow_array;
@hash{ @{ $sth->{NAME_uc} } } = @row if @row;
$sth->finish;
};
if ($@)
{
my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return %hash;
}
sub column
{
my $self = shift;
my %p = @_;
$self->_ensure_valid_dbh;
my $sth = $self->_prepare_and_execute(%p);
my @data;
eval
{
my @row;
$sth->bind_columns( \ (@row[ 0..$#{ $sth->{NAME_lc} } ] ) );
push @data, $row[0] while ($sth->fetch);
$sth->finish;
};
if ($@)
{
my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return wantarray ? @data : $data[0];
}
use constant _PREPARE_AND_EXECUTE_SPEC => { sql => { type => SCALAR },
bind => { type => UNDEF | SCALAR | ARRAYREF,
optional => 1 },
};
sub _prepare_and_execute
{
my $self = shift;
validate( @_, _PREPARE_AND_EXECUTE_SPEC );
my %p = @_;
Alzabo::Exception::Driver->throw( error => "Attempt to access the database without database handle. Was ->connect called?" )
unless $self->{dbh};
my @bind = exists $p{bind} ? ( ref $p{bind} ? @{ $p{bind} } : $p{bind} ) : ();
my $sth;
eval
{
$sth = $self->{dbh}->prepare( $p{sql} );
$sth->execute(@bind);
};
if ($@)
{
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return $sth;
}
sub do
{
my $self = shift;
my %p = @_;
lib/Alzabo/Driver.pm view on Meta::CPAN
my $sth = $self->_prepare_and_execute(%p);
my $rows;
eval
{
$rows = $sth->rows;
$sth->finish;
};
if ($@)
{
my @bind = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [$p{bind}] ) : ();
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => \@bind );
}
return $rows;
}
sub tables
{
my $self = shift;
$self->_ensure_valid_dbh;
lib/Alzabo/Driver.pm view on Meta::CPAN
{
my $self = shift->new_no_execute(@_);
$self->execute;
return $self;
}
use constant NEW_NO_EXECUTE_SPEC => { dbh => { can => 'prepare' },
sql => { type => SCALAR },
bind => { type => SCALAR | ARRAYREF,
optional => 1 },
limit => { type => UNDEF | ARRAYREF,
optional => 1 },
};
sub new_no_execute
{
my $proto = shift;
my $class = ref $proto || $proto;
lib/Alzabo/Driver.pm view on Meta::CPAN
my $self = bless {}, $class;
$self->{limit} = $p{limit} ? $p{limit}[0] : 0;
$self->{offset} = $p{limit} && $p{limit}[1] ? $p{limit}[1] : 0;
$self->{rows_fetched} = 0;
eval
{
$self->{sth} = $p{dbh}->prepare( $p{sql} );
$self->{bind} = exists $p{bind} ? ( ref $p{bind} ? $p{bind} : [ $p{bind} ] ) : [];
};
Alzabo::Exception::Driver->throw( error => $@,
sql => $p{sql},
bind => $self->{bind} ) if $@;
return $self;
}
sub execute
{
my $self = shift;
eval
{
$self->{sth}->finish if $self->{sth}->{Active};
$self->{rows_fetched} = 0;
$self->{sth}->execute( @_ ? @_ : @{ $self->{bind} } );
$self->{result} = [];
$self->{count} = 0;
$self->{sth}->bind_columns
( \ ( @{ $self->{result} }[ 0..$#{ $self->{sth}->{NAME_lc} } ] ) );
};
Alzabo::Exception::Driver->throw( error => $@,
sql => $self->{sth}{Statement},
bind => $self->{bind} ) if $@;
}
sub execute_no_result
{
my $self = shift;
eval
{
$self->{sth}->execute(@_);
};
Alzabo::Exception::Driver->throw( error => $@,
sql => $self->{sth}{Statement},
bind => $self->{bind} ) if $@;
}
sub next
{
my $self = shift;
my %p = @_;
return unless $self->{sth}->{Active};
my $active;
eval
{
do
{
$active = $self->{sth}->fetch;
} while ( $active && $self->{rows_fetched}++ < $self->{offset} );
};
Alzabo::Exception::Driver->throw( error => $@,
sql => $self->{sth}{Statement},
bind => $self->{bind} ) if $@;
return unless $active;
$self->{count}++;
return wantarray ? @{ $self->{result} } : $self->{result}[0];
}
sub next_as_hash
{
lib/Alzabo/Driver.pm view on Meta::CPAN
my $active;
eval
{
do
{
$active = $self->{sth}->fetch;
} while ( $active && $self->{rows_fetched}++ < $self->{offset} );
};
Alzabo::Exception::Driver->throw( error => $@,
sql => $self->{sth}{Statement},
bind => $self->{bind} ) if $@;
return unless $active;
my %hash;
@hash{ @{ $self->{sth}->{NAME_lc} } } = @{ $self->{result} };
$self->{count}++;
return %hash;
}
lib/Alzabo/Driver.pm view on Meta::CPAN
while (my %h = $self->next_as_hash)
{
push @rows, \%h;
}
$self->{count} = scalar @rows;
return @rows;
}
sub bind
{
my $self = shift;
return @{ $self->{bind} };
}
sub count { $_[0]->{count} }
sub DESTROY
{
my $self = shift;
local $@;
eval { $self->{sth}->finish if $self->{sth}; };
lib/Alzabo/Driver.pm view on Meta::CPAN
use the functionality provided by the
L<C<Alzabo::DriverStatement>|Alzabo::DriverStatement> class, which
allows you to fetch results one row at a time.
These methods all accept the following parameters:
=over 4
=item * sql => $sql_string
=item * bind => $bind_value or \@bind_values
=item * limit => [ $max, optional $offset ] (optional)
The C<$offset> defaults to 0.
This parameter has no effect for the methods that return only one
row. For the others, it causes the drivers to skip C<$offset> rows
and then return only C<$max> rows. This is useful if the RDBMS being
used does not support C<LIMIT> clauses.
lib/Alzabo/Driver.pm view on Meta::CPAN
Throws: L<C<Alzabo::Exception::Driver>|Alzabo::Exceptions>
=head2 all_rows_hash
Returns an array of hashes, each hash representing a single row
returned from the database. The hash keys are all in lowercase.
Throws: L<C<Alzabo::Exception::Driver>|Alzabo::Exceptions>
=head2 execute (@bind_values)
Executes the associated statement handle with the given bound
parameters. If the statement handle is still active (it was
previously executed and has more data left) then its C<finish()>
method will be called first.
Throws: L<C<Alzabo::Exception::Driver>|Alzabo::Exceptions>
=head2 count
lib/Alzabo/Driver.pm view on Meta::CPAN
In addition to the methods inherited from
L<C<Exception::Class::Base>|Exception::Class::Base>, objects in this
class also contain several methods specific to this subclass.
=head2 sql
Returns the SQL statement in use at the time the error occurred, if
any.
=head2 bind
Returns an array reference contaning the bound parameters for the SQL
statement, if any.
=head1 SUBCLASSING Alzabo::Driver
To create a subclass of C<Alzabo::Driver> for your particular RDBMS is
fairly simple. First of all, there must be a C<DBD::*> driver for it,
as C<Alzabo::Driver> is built on top of C<DBI>.
lib/Alzabo/Exceptions.pm view on Meta::CPAN
BEGIN
{
%e = ( 'Alzabo::Exception' =>
{ description =>
'Generic exception within the Alzabo API. Should only be used as a base class.',
alias => 'exception',
},
'Alzabo::Exception::Driver' =>
{ description => 'An attempt to eval a string failed',
fields => [ 'sql', 'bind' ],
isa => 'Alzabo::Exception',
alias => 'driver_exception',
},
'Alzabo::Exception::Eval' =>
{ description => 'An attempt to eval a string failed',
isa => 'Alzabo::Exception',
alias => 'eval_exception',
},
lib/Alzabo/Exceptions.pm view on Meta::CPAN
package Alzabo::Exception::Driver;
sub full_message
{
my $self = shift;
my $msg = $self->error;
$msg .= "\nSQL: " . $self->sql if $self->sql;
if ( $self->bind )
{
my @bind = map { defined $_ ? $_ : '<undef>' } @{ $self->bind };
$msg .= "\nBIND: @bind" if @bind;
}
return $msg;
}
1;
=head1 NAME
Alzabo::Exceptions - Creates all exception subclasses used in Alzabo.
lib/Alzabo/RDBMSRules/MySQL.pm view on Meta::CPAN
sub reverse_engineer
{
my $self = shift;
my $schema = shift;
my $driver = $schema->driver;
my $has_table_types =
$driver->one_row( sql => 'SHOW VARIABLES LIKE ?',
bind => 'table_type' );
foreach my $table ( $driver->tables )
{
my $table_name = $self->_clean_table_name($table);
my $t = $schema->make_table( name => $table_name );
foreach my $row ( $driver->rows( sql => "DESCRIBE $table" ) )
{
my ($type, @a);
lib/Alzabo/RDBMSRules/MySQL.pm view on Meta::CPAN
{
$t->make_index( columns => $i{$index}{cols},
unique => $i{$index}{unique},
fulltext => $i{$index}{fulltext} );
}
if ( $has_table_types )
{
my $table_type =
( $driver->one_row( sql => 'SHOW TABLE STATUS LIKE ?',
bind => $table_name ) )[1];
$t->add_attribute( 'TYPE=' . uc $table_type );
}
}
}
my %ignored_defaults = ( DATETIME => '0000-00-00 00:00:00',
DATE => '0000-00-00',
YEAR => '0000',
CHAR => '',
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
{
$table =~ s/^[^\.]+\.//;
$table =~ s/^\"|\"$//g;
print STDERR "Adding table $table to schema\n"
if Alzabo::Debug::REVERSE_ENGINEER;
my $t = $schema->make_table( name => $table );
my $t_oid = $driver->one_row( sql => 'SELECT oid FROM pg_class WHERE relname = ?',
bind => $table );
my $sql = <<'EOF';
SELECT a.attname, a.attnotnull, t.typname, a.attnum, a.atthasdef, a.atttypmod
FROM pg_attribute a, pg_type t
WHERE a.attrelid = ?
AND a.atttypid = t.oid
AND a.attnum > 0
EOF
$sql .= ' AND NOT a.attisdropped' if $driver->rdbms_version ge '7.3';
$sql .= ' ORDER BY attnum';
my %cols_by_number;
foreach my $row ( $driver->rows( sql => $sql,
bind => $t_oid ) )
{
my %p;
$p{type} = $row->[2];
# has default
if ( $row->[4] )
{
$p{default} =
$driver->one_row
( sql => 'SELECT adsrc FROM pg_attrdef WHERE adrelid = ? AND adnum = ?',
bind => [ $t_oid, $row->[3] ] );
if ( $p{default} =~ /^nextval\(/ )
{
$p{sequenced} = 1;
$p{type} =~ s/(?:int(?:eger)?|numeric)/serial/;
}
else
{
# strip quotes (and type!) Postgres added
$p{default} =~ s/^'//; #'
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
$sql = <<'EOF';
SELECT indkey
FROM pg_index
WHERE indisprimary
AND indrelid = ?
EOF
foreach my $cols ( $driver->column( sql => $sql,
bind => $t_oid ) )
{
my @cols = @cols_by_number{ split ' ', $cols };
local $" = ", ";
print STDERR "Setting @cols as primary key for $table\n"
if Alzabo::Debug::REVERSE_ENGINEER;
$t->add_primary_key( $_ ) for $t->columns( @cols );
}
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
$sql = <<'EOF';
SELECT consrc, array_to_string(conkey,' ')
FROM pg_constraint
WHERE conrelid = ?
AND contype = 'c'
EOF
my @att;
foreach my $row ( $driver->rows( sql => $sql,
bind => $t_oid ) )
{
my ( $con, $cols ) = @$row;
# this stuff is not needed
$con =~ s/::(\w+)//g;
# If $cols ever covers more than one value then this will fail.
if ( $cols =~ /^(\d+)$/ )
{
my $column = $cols_by_number{$1};
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
my $seq_name = $self->_sequence_name($col);
my $sql = <<'EOF';
SELECT 1
FROM pg_class
WHERE relname = ?
AND relkind = ?
EOF
return $driver->one_row( sql => $sql,
bind => [ $seq_name, 'S' ],
);
}
sub _74_indexes
{
my $self = shift;
my $driver = shift;
my $table = shift;
my $t_oid = shift;
my $cols_by_number = shift;
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
my $sql = <<'EOF';
SELECT indexrelid, indisunique, indkey, indnatts
FROM pg_index
WHERE indrelid = ?
AND NOT indisprimary
EOF
my %i;
INDEX:
foreach my $row ( $driver->rows( sql => $sql,
bind => $t_oid ) )
{
my $function;
my @col_numbers;
my $spi =
$driver->one_row
( sql => "SELECT COALESCE(indexprs,'') FROM pg_index WHERE indexrelid = ?",
bind => $row->[0] );
if ( $spi )
{
SPI_EXPRESSION:
while ( my $spi_expr =
Text::Balanced::extract_bracketed( $spi, '{}', '[^{}]*' ) )
{
# A wanton lack of respect for boundaries. 'Parse' the
# PostgreSQL internal SPI language to find out what
# columns are being accessed.
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
die "Alzabo " . Alzabo->VERSION . " does not support functional"
. " indexes that are not strictly a single function."
. " There are multiple functions on an index on the "
. $table->name() . " table.\n";
}
elsif ( scalar( @col_numbers ) == 1 )
{
my $func =
$driver->one_row
( sql => 'SELECT pg_catalog.pg_get_indexdef( ?, 1, true)',
bind => $row->[0] );
# XXX - not sure if this is a good idea but it makes the
# rev-eng tests pass
$func =~ s/\b(\w+)::\w+\b/$1/g;
my $col_in_func = $1;
my @function;
for my $num ( split / +/, $row->[2] )
{
if ( $num == 0 )
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
WHERE i.indrelid = ?
AND NOT i.indisprimary
AND i.indexrelid = c.oid
AND c.oid = a.attrelid
AND a.attnum > 0
ORDER BY a.attnum
EOF
my %i;
foreach my $row ( $driver->rows( sql => $sql,
bind => $t_oid ) )
{
my @col_names = @{ $cols_by_number }{ split ' ', $row->[4] };
my $function;
if ( $row->[3] && $row->[3] =~ /\w/ && $row->[3] ne '-' )
{
# some function names come out as "pg_catalog.foo"
$row->[3] =~ s/\w+\.(\w+)/$1/;
$function = uc $row->[3];
$function .= '(';
lib/Alzabo/RDBMSRules/PostgreSQL.pm view on Meta::CPAN
my $column_sql = <<'EOF';
SELECT attname
FROM pg_attribute
WHERE attrelid = ?
AND attnum = ?
EOF
foreach my $row ( $driver->rows( sql => $constraint_sql ) )
{
my $from_table = $driver->one_row( sql => $table_sql,
bind => $row->[0] );
my $to_table = $driver->one_row( sql => $table_sql,
bind => $row->[1] );
# Column numbers are given as strings like "3 5"
my @from_cols = split ' ', $row->[2]
or die "Weird column specification $row->[2]";
my @to_cols = split ' ', $row->[3]
or die "Weird column specification $row->[3]";
# Convert column numbers to names
foreach (@from_cols)
{
$_ = $driver->one_row( sql => $column_sql,
bind => [$row->[0], $_] );
}
foreach (@to_cols)
{
$_ = $driver->one_row( sql => $column_sql,
bind => [$row->[1], $_] );
}
print STDERR "Adding $from_table foreign key to $to_table\n"
if Alzabo::Debug::REVERSE_ENGINEER;
# Convert to Alzabo objects
$from_table = $schema->table($from_table);
$to_table = $schema->table($to_table);
@from_cols = map { $from_table->column($_) } @from_cols;
@to_cols = map { $to_table->column($_) } @to_cols;
lib/Alzabo/Runtime/Cursor.pm view on Meta::CPAN
my $sub = (caller(1))[3];
Alzabo::Exception::VirtualMethod->throw
( error =>
"$sub is a virtual method and must be subclassed in " . ref $self );
}
sub reset
{
my $self = shift;
$self->{statement}->execute( $self->{statement}->bind );
$self->{count} = 0;
}
sub count
{
my $self = shift;
return $self->{count};
}
lib/Alzabo/Runtime/RowState/Live.pm view on Meta::CPAN
select( ($row->table->primary_key)[0] )->
from( $row->table ) );
$class->_where($row, $sql);
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
return
unless defined $row->schema->driver->one_row( sql => $sql->sql,
bind => $sql->bind );
}
return 1;
}
sub _get_prefetch_data
{
my $class = shift;
my $row = shift;
lib/Alzabo/Runtime/RowState/Live.pm view on Meta::CPAN
select( $row->table->columns(@select) )->
from( $row->table ) );
$class->_where($row, $sql);
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
my %d;
@d{@select} =
$row->schema->driver->one_row( sql => $sql->sql,
bind => $sql->bind )
or $row->_no_such_row_error;
while ( my( $k, $v ) = each %d )
{
$row->{data}{$k} = $data{$k} = $v;
}
return %data;
}
lib/Alzabo/Runtime/RowState/Live.pm view on Meta::CPAN
{
foreach my $fk (@fk)
{
$fk->register_update( map { $_->name => $data{ $_->name } } $fk->columns_from );
}
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
$schema->driver->do( sql => $sql->sql,
bind => $sql->bind );
$schema->commit if @fk;
};
if (my $e = $@)
{
eval { $schema->rollback };
rethrow_exception $e;
}
lib/Alzabo/Runtime/RowState/Live.pm view on Meta::CPAN
{
foreach my $fk (@fk)
{
$fk->register_delete($row);
}
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
$schema->driver->do( sql => $sql->sql,
bind => $sql->bind );
$schema->commit if @fk;
};
if (my $e = $@)
{
eval { $schema->rollback };
rethrow_exception $e;
}
lib/Alzabo/Runtime/Schema.pm view on Meta::CPAN
Alzabo::Runtime::process_order_by_clause( $sql, $p{order_by} )
if $p{order_by};
$sql->limit( ref $p{limit} ? @{ $p{limit} } : $p{limit} ) if $p{limit};
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
my $statement = $self->driver->statement( sql => $sql->sql,
bind => $sql->bind );
if (@select_tables == 1)
{
return Alzabo::Runtime::RowCursor->new
( statement => $statement,
table => $select_tables[0]->real_table,
);
}
else
{
lib/Alzabo/Runtime/Schema.pm view on Meta::CPAN
my $sql = $self->_select_sql(%p);
my $method =
Alzabo::Utils::is_arrayref( $p{select} ) && @{ $p{select} } > 1 ? 'rows' : 'column';
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
return $self->driver->$method( sql => $sql->sql,
bind => $sql->bind );
}
sub select
{
my $self = shift;
my $sql = $self->_select_sql(@_);
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
return $self->driver->statement( sql => $sql->sql,
bind => $sql->bind );
}
use constant _SELECT_SQL_SPEC => { join => { type => ARRAYREF | OBJECT,
optional => 1 },
tables => { type => ARRAYREF | OBJECT,
optional => 1 },
select => { type => SCALAR | ARRAYREF | OBJECT,
optional => 1 },
where => { type => ARRAYREF,
optional => 1 },
lib/Alzabo/Runtime/Table.pm view on Meta::CPAN
{
foreach my $fk (@fk)
{
$fk->register_insert( map { $_->name => $vals->{ $_->name } } $fk->columns_from );
}
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
$self->schema->driver->do( sql => $sql->sql,
bind => $sql->bind );
foreach my $pk (@pk)
{
$id{ $pk->name } = ( defined $vals->{ $pk->name } ?
$vals->{ $pk->name } :
$schema->driver->get_last_id($self) );
}
# must come after call to ->get_last_id for MySQL because the
# id will no longer be available after the transaction ends.
lib/Alzabo/Runtime/Table.pm view on Meta::CPAN
if ( exists $p{limit} )
{
$sql->limit( ref $p{limit} ? @{ $p{limit} } : $p{limit} );
}
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
my @return = $self->schema->driver->one_row( sql => $sql->sql,
bind => $sql->bind )
or return;
my @pk = $self->primary_key;
my (%pk, %prefetch);
@pk{ map { $_->name } @pk } = splice @return, 0, scalar @pk;
# Must be some prefetch pieces
if (@return)
lib/Alzabo/Runtime/Table.pm view on Meta::CPAN
( map { $_ => { optional => 1 } } keys %p ) } );
Alzabo::Runtime::process_order_by_clause( $p{sql}, $p{order_by} ) if exists $p{order_by};
if ( exists $p{limit} )
{
$p{sql}->limit( ref $p{limit} ? @{ $p{limit} } : $p{limit} );
}
my $statement = $self->schema->driver->statement( sql => $p{sql}->sql,
bind => $p{sql}->bind,
limit => $p{sql}->get_limit );
return Alzabo::Runtime::RowCursor->new( statement => $statement,
table => $self,
);
}
sub potential_row
{
my $self = shift;
lib/Alzabo/Runtime/Table.pm view on Meta::CPAN
my $sql = $self->_select_sql(%p);
my $method =
Alzabo::Utils::is_arrayref( $p{select} ) && @{ $p{select} } > 1 ? 'rows' : 'column';
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
return $self->schema->driver->$method( sql => $sql->sql,
bind => $sql->bind );
}
sub select
{
my $self = shift;
my $sql = $self->_select_sql(@_);
$sql->debug(\*STDERR) if Alzabo::Debug::SQL;
print STDERR Devel::StackTrace->new if Alzabo::Debug::TRACE;
return $self->schema->driver->statement( sql => $sql->sql,
bind => $sql->bind );
}
use constant
_SELECT_SQL_SPEC => { select => { type => SCALAR | ARRAYREF | OBJECT },
where => { type => ARRAYREF | OBJECT,
optional => 1 },
order_by => { type => ARRAYREF | HASHREF | OBJECT,
optional => 1 },
group_by => { type => ARRAYREF | HASHREF | OBJECT,
optional => 1 },
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
sub new
{
my $class = shift;
my %p = validate( @_, NEW_SPEC );
return bless { last_op => undef,
expect => undef,
type => undef,
sql => '',
bind => [],
placeholders => [],
as_id => 'aaaaa10000',
alias_in_having => 1,
%p,
}, $class;
}
# this just needs to be some unique thing that won't ever look like a
# valid bound parameter
my $placeholder = do { my $x = 1; bless \$x, 'Alzabo::SQLMaker::Placeholder' };
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
@{ $self->{tables} }{ $join_from, $join_on } = (1, 1);
if ($where)
{
$sql .= ' AND ';
# make a clone
my $sql_maker = bless { %$self }, ref $self;
$sql_maker->{sql} = '';
# sharing same ref intentionally
$sql_maker->{bind} = $self->{bind};
$sql_maker->{tables} = $self->{tables};
# lie to Alzabo::Runtime::process_where_clause
$sql_maker->{last_op} = 'where';
Alzabo::Runtime::process_where_clause( $sql_maker, $where );
$sql .= $sql_maker->sql;
$sql .= ' ';
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
$err .= ' clause';
Alzabo::Exception::SQL->throw( error => $err );
}
return ( $self->{quote_identifiers} ?
$self->{driver}->quote_identifier( $rhs->table->alias_name, $rhs->name ) :
$rhs->table->alias_name . '.' . $rhs->name );
}
else
{
return $self->_bind_val($rhs);
}
}
sub _subselect
{
my $self = shift;
my $sql = shift;
push @{ $self->{bind} }, @{ $sql->bind };
return $sql->sql;
}
sub order_by
{
my $self = shift;
$self->_assert_last_op( qw( select from condition group_by ) );
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
{
my $self = shift;
$self->_assert_last_op( qw( into ) );
validate_pos( @_, ( { type => UNDEF | SCALAR | OBJECT } ) x @_ );
if ( ref $_[0] && $_[0]->isa('Alzabo::SQLMaker') )
{
$self->{sql} = $_[0]->sql;
push @{ $self->{bind} }, $_[0]->bind;
}
else
{
my @vals = @_;
Alzabo::Exception::Params->throw
( error => "'values' method expects key/value pairs of column objects and values'" )
if !@vals || @vals % 2;
my %vals = map { ref $_ && $_->can('table') ? $_->name : $_ } @vals;
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
foreach my $c ( @{ $self->{columns } } )
{
Alzabo::Exception::SQL->throw
( error => $c->name . " was specified in the into method call but no value was provided" )
unless exists $vals{ $c->name };
}
$self->{sql} .= 'VALUES (';
$self->{sql} .=
join ', ', ( map { $self->_bind_val_for_insert( $_, $vals{ $_->name } ) }
@{ $self->{columns} }
);
$self->{sql} .= ')';
}
if ( @{ $self->{placeholders} } && @{ $self->{bind} } )
{
Alzabo::Exception::SQL->throw
( error => "Cannot mix actual bound values and placeholders in call to values()" );
}
$self->{last_op} = 'values';
return $self;
}
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
my $err = 'Cannot set column (';
$err .= join '.', $col->table->name, $col->name;
$err .= ') unless its table is included in the UPDATE clause';
Alzabo::Exception::SQL->throw( error => $err );
}
push @set,
( $self->{quote_identifiers} ?
$self->{driver}->quote_identifier( $col->name ) :
$col->name ) .
' = ' . $self->_bind_val($val);
}
$self->{sql} .= join ', ', @set;
$self->{last_op} = 'set';
return $self;
}
sub delete
{
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
$op =~ s/.*::(.*?)$/$1/;
Alzabo::Exception::SQL->throw( error => "Cannot follow $self->{last_op} with $op" );
}
}
use constant _BIND_VAL_FOR_INSERT_SPEC => ( { isa => 'Alzabo::Runtime::Column' },
{ type => UNDEF | SCALAR | OBJECT }
);
sub _bind_val_for_insert
{
my $self = shift;
my ( $col, $val ) =
validate_pos( @_, _BIND_VAL_FOR_INSERT_SPEC );
if ( defined $val && $val eq $placeholder )
{
push @{ $self->{placeholders} }, $col->name;
return '?';
}
else
{
return $self->_bind_val($val);
}
}
use constant _BIND_VAL_SPEC => { type => UNDEF | SCALAR | OBJECT };
sub _bind_val
{
my $self = shift;
validate_pos( @_, _BIND_VAL_SPEC );
return $_[0]->as_string( $self->{driver}, $self->{quote_identifiers} )
if Alzabo::Utils::safe_isa( $_[0], 'Alzabo::SQLMaker::Function' );
push @{ $self->{bind} }, $_[0];
return '?';
}
sub sql
{
my $self = shift;
Alzabo::Exception::SQL->throw( error => "SQL contains unbalanced parentheses subgrouping: $self->{sql}" )
if $self->{subgroup};
return $self->{sql};
}
sub bind
{
my $self = shift;
return $self->{bind};
}
sub placeholders
{
my $self = shift;
my $x = 0;
return map { $_ => $x++ } @{ $self->{placeholders} };
}
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
sub debug
{
my $self = shift;
my $fh = shift;
print $fh '-' x 75 . "\n";
print $fh "SQL\n - " . $self->sql . "\n";
print $fh "Bound values\n";
foreach my $b ( @{ $self->bind } )
{
my $out = $b;
if ( defined $out )
{
if ( length $out > 75 )
{
$out = substr( $out, 0, 71 ) . ' ...';
}
}
lib/Alzabo/SQLMaker.pm view on Meta::CPAN
=head2 sql
This method can be called at any time, though obviously it will not
return valid SQL unless called at a natural end point. In the future,
an exception may be thrown if called when the SQL is not in a valid
state.
Returns the SQL generated so far as a string.
=head2 bind
Returns an array reference containing the parameters to be bound to
the SQL statement.
=head1 SUBCLASSING Alzabo::SQLMaker
To create a subclass of C<Alzabo::SQLMaker> for your particular RDBMS
requires only that the L<virtual methods|"Virtual Methods"> listed
below be implemented.
t/14-unique-row-cache.t view on Meta::CPAN
{
my $dep2 = $s->table('department')->insert( values => { name => 'dep2' } );
my $dep2_copy =
$s->table('department')->row_by_pk( pk => $dep2->select('department_id') );
$dep2->update( name => 'foo' );
is( $dep2_copy->select('name'), 'foo', 'name in copy is foo' );
$s->driver->do( sql => 'UPDATE department SET name = ? WHERE department_id = ?',
bind => [ 'bar', $dep2->select('department_id') ],
);
$dep2->refresh;
is( $dep2->select('name'), 'bar', 'refresh works for cached rows' );
is( $dep2_copy->select('name'), 'bar', 'refresh works for cached rows' );
my $old_id = $dep2->id_as_string;
{
my $updated = $dep2->update( department_id => 1000 );