DBIx-DBO

 view release on metacpan or  search on metacpan

lib/DBIx/DBO/Query.pm  view on Meta::CPAN

package DBIx::DBO::Query;

use 5.014;
use warnings;
use DBIx::DBO;

use Carp 'croak';
use Devel::Peek 'SvREFCNT';
use Hash::Util 'hv_store';
use Scalar::Util 'weaken';

use overload '**' => \&column, fallback => 1;

sub table_class { $_[0]{DBO}->table_class }
sub row_class { $_[0]{DBO}->row_class }

*_isa = \&DBIx::DBO::DBD::_isa;

=head1 NAME

DBIx::DBO::Query - An OO interface to SQL queries and results.  Encapsulates an entire query in an object.

=head1 SYNOPSIS

  # Create a Query object by JOINing 2 tables
  my $query = $dbo->query('my_table', 'my_other_table');
  
  # Get the Table objects from the query
  my($table1, $table2) = $query->tables;
  
  # Add a JOIN ON clause
  $query->join_on($table1 ** 'login', '=', $table2 ** 'username');
  
  # Find our ancestors, and order by age (oldest first)
  $query->where('name', '=', 'Adam');
  $query->where('name', '=', 'Eve');
  $query->order_by({ COL => 'age', ORDER => 'DESC' });
  
  # New Query using a LEFT JOIN
  ($query, $table1) = $dbo->query('my_table');
  $table2 = $query->join_table('another_table', 'LEFT');
  $query->join_on($table1 ** 'parent_id', '=', $table2 ** 'child_id');
  
  # Find those not aged between 20 and 30.
  $query->where($table1 ** 'age', '<', 20, FORCE => 'OR'); # Force OR so that we get: (age < 20 OR age > 30)
  $query->where($table1 ** 'age', '>', 30, FORCE => 'OR'); # instead of the default: (age < 20 AND age > 30)

=head1 DESCRIPTION

A C<Query> object represents rows from a database (from one or more tables). This module makes it easy, not only to fetch and use the data in the returned rows, but also to modify the query to return a different result set.

=head1 METHODS

=head3 C<new>

  DBIx::DBO::Query->new($dbo, $table1, ...);
  # or
  $dbo->query($table1, ...);

Create a new C<Query> object from the tables specified.
In scalar context, just the C<Query> object will be returned.
In list context, the C<Query> object and L<Table|DBIx::DBO::Table> objects will be returned for each table specified.
Tables can be specified with the same arguments as L<DBIx::DBO::Table/new> or another Query can be used as a subquery.

  my($query, $table1, $table2) = DBIx::DBO::Query->new($dbo, 'customers', ['history', 'transactions']);

You can also pass in a Query instead of a Table to use that query as a subquery.

  my $subquery = DBIx::DBO::Query->new($dbo, 'history.transactions');
  my $query = DBIx::DBO::Query->new($dbo, 'customers', $subquery);

lib/DBIx/DBO/Query.pm  view on Meta::CPAN

Returns undefined if there is an error or is unable to determine the number of found rows.

=cut

sub found_rows {
    my $me = shift;
    return $me->{Found_Rows} // $me->{DBO}{dbd_class}->_calc_found_rows($me);
}

=head3 C<update>

  $query->update(department => 'Tech');
  $query->update(salary => { FUNC => '? * 1.10', COL => 'salary' }); # 10% raise

Updates every row in the query with the new values specified.
Returns the number of rows updated or C<'0E0'> for no rows to ensure the value is true,
and returns false if there was an error.

=cut

sub update {
    my $me = shift;
    my @update = $me->{DBO}{dbd_class}->_parse_set($me, @_);
    my $sql = $me->{DBO}{dbd_class}->_build_sql_update($me, @update);
    $me->{DBO}{dbd_class}->_do($me, $sql, undef, $me->{DBO}{dbd_class}->_bind_params_update($me));
}

=head3 C<sql>

  my $sql = $query->sql;

Returns the SQL statement string.

=cut

sub _recursion_check {
    my($me, @upquery) = @_;

    state @_recursion_check;
    push @_recursion_check, $me;

    for my $upquery (@upquery) {
        if (grep $upquery eq $_, @_recursion_check) {
            undef @_recursion_check;
            croak 'Recursive subquery found';
        }
        exists $upquery->{up_queries}
            and $upquery->_recursion_check(grep defined($_), @{ $upquery->{up_queries} });
    }

    pop @_recursion_check;
}

sub _add_up_query {
    my($me, $upquery) = @_;

    $me->_recursion_check($upquery);

    my $uq = $me->{up_queries} //= [];
    push @$uq, $upquery;
    weaken $uq->[-1];
}

sub sql {
    my $me = shift;
    return $me->{DBO}{dbd_class}->_build_sql_select($me);
}

sub _inactivate {
    my $me = shift;
    $me->_empty_row;
    # Also inactivate super queries
    if (exists $me->{up_queries}) {
        defined $_ and $_->_inactivate for @{ $me->{up_queries} };
    }
    # Reset the query
    delete $me->{cache};
    undef $me->{sth};
    undef $me->{sql};
    undef $me->{bind};
    undef $me->{hash};
    undef $me->{Active};
    undef $me->{Row_Count};
    undef $me->{Found_Rows};
    undef @{$me->{Columns}};
}

=head3 C<finish>

  $query->finish;

Calls L<DBI-E<gt>finish|DBI/"finish"> on the statement handle, if it's active.
Restarts cached queries from the first row (if created using the C<CacheQuery> config).
This ensures that the next call to L</fetch> will return the first row from the query.

=cut

sub finish {
    my $me = shift;
    $me->_empty_row;
    # Restart the query
    if (exists $me->{cache}) {
        $me->{cache}{idx} = 0;
    } else {
        $me->{sth}->finish if $me->{sth} and $me->{sth}{Active};
        $me->{Active} = 0;
    }
}

sub _empty_row {
    my $me = shift;
    # Detach or empty the Row
    if (defined $me->{Row}) {
        if (SvREFCNT(${$me->{Row}}) > 1) {
            $me->{Row}->_detach;
        } else {
            undef ${$me->{Row}}->{array};
            ${$me->{Row}}->{hash} = {};
        }
    }
}



( run in 2.426 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )