Alzabo

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

- 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.

Changes  view on Meta::CPAN


- 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 );



( run in 1.604 second using v1.01-cache-2.11-cpan-2398b32b56e )