[Jifty-commit] r5496 - Jifty-DBI/branches/tisql/lib/Jifty/DBI

Jifty commits jifty-commit at lists.jifty.org
Sat Jun 7 11:48:33 EDT 2008


Author: ruz
Date: Sat Jun  7 11:48:33 2008
New Revision: 5496

Modified:
   Jifty-DBI/branches/tisql/lib/Jifty/DBI/Tisql.pm

Log:
* implement merge joins
* move towards implementing conditions bundling
* changes here and there

Modified: Jifty-DBI/branches/tisql/lib/Jifty/DBI/Tisql.pm
==============================================================================
--- Jifty-DBI/branches/tisql/lib/Jifty/DBI/Tisql.pm	(original)
+++ Jifty-DBI/branches/tisql/lib/Jifty/DBI/Tisql.pm	Sat Jun  7 11:48:33 2008
@@ -6,6 +6,8 @@
 use base qw(Parse::BooleanLogic);
 use Scalar::Util qw(refaddr blessed);
 
+use Data::Dumper;
+
 use Regexp::Common qw(delimited);
 my $re_delim  = qr{$RE{delimited}{-delim=>qq{\'\"}}};
 my $re_field  = qr{[a-zA-Z][a-zA-Z0-9_]*};
@@ -36,40 +38,46 @@
     my $string = shift;
 
     my $tree = {
-        joins => {},
+        aliases => {},
         conditions => undef,
     };
-    
-    # parse "FROM..." prefix into $tree->{'joins'}
+
+    # parse "FROM..." prefix into $tree->{'aliases'}
     if ( $string =~ s/^\s*FROM\s+($re_alias(?:\s*,\s*$re_alias)*)\s+WHERE\s+//oi ) {
-        $tree->{'joins'}->{ $_->[1] } = $self->find_column( $_->[0], $tree->{'joins'} )
-            foreach map [split /\s+AS\s+/i, $_], split /,/, $1;
+        $tree->{'aliases'}->{ $_->[1] } = $self->find_column( $_->[0], $tree->{'aliases'} )
+            foreach map [split /\s+AS\s+/i, $_], split /\s*,\s*/, $1;
+        while ( my ($name, $meta) = each %{ $tree->{aliases} } ) {
+            $meta->{'name'} = $name;
+        }
     }
 
     $tree->{'conditions'} = $self->as_array(
         $string,
-        operand_cb => sub { return $self->split_condition( $_[0], $tree->{'joins'} ) },
+        operand_cb => sub { return $self->parse_condition( $_[0], $tree->{'aliases'} ) },
     );
-    use Data::Dumper; warn Dumper( $tree->{'conditions'} );
-    $self->apply_query_tree( $tree->{'conditions'} );
+    $self->{'tisql'}{'conditions'} = $tree->{'conditions'};
+    $self->merge_joins( $tree );
+    $self->bundle_conditions( $tree ) if 0;
+    $self->apply_query_tree( $tree );
     return $tree;
 }
 
 sub apply_query_tree {
     my $self = shift;
     my $tree = shift;
+    my $current = shift || $tree->{'conditions'};
+    my $ea = shift || 'AND';
 
     my $collection = $self->{'collection'};
 
-    my $ea = shift || 'AND';
     $collection->open_paren('tisql');
-    foreach my $element ( @$tree ) {
+    foreach my $element ( @$current ) {
         unless ( ref $element ) {
             $ea = $element;
             next;
         }
         elsif ( ref $element eq 'ARRAY' ) {
-            $self->apply_query_tree( $element, $ea );
+            $self->apply_query_tree( $tree, $element, $ea );
             next;
         }
         elsif ( ref $element ne 'HASH' ) {
@@ -82,14 +90,16 @@
             operator         => $element->{'op'},
         );
         if ( ref $element->{'lhs'} ) {
-            my ($alias, $column) = $self->resolve_join( @{ $element->{'lhs'} } );
-            @limit{qw(alias column)} = ($alias, $column->name);
+            $limit{'alias'} = $self->resolve_join( $element->{'lhs'} );
+            $limit{'column'} = $element->{'lhs'}{'chain'}[-1]->name;
         } else {
             die "left hand side must be always column specififcation";
         }
         if ( ref $element->{'rhs'} ) {
-            my ($alias, $column) = $self->resolve_join( @{ $element->{'rhs'} } );
-            @limit{qw(quote_value value)} = (0, $alias .'.'. $column->name );
+            $limit{'quote_value'} = 0;
+            $limit{'value'} =
+                $self->resolve_join( $element->{'rhs'} )
+                .'.'. $element->{'rhs'}{'chain'}[-1]->name;
         } else {
             $limit{'value'} = $element->{'rhs'};
         }
@@ -99,35 +109,89 @@
     $collection->close_paren('tisql');
 }
 
-sub resolve_join {
-    my $self = shift;
-    my @chain = @_;
-    if ( @chain == 1 ) {
-        return 'main', $chain[0];
+{
+my %cache;
+my $i = 0;
+my $aliases;
+my $merge_joins_cb = sub {
+    my $meta = shift;
+    my @parts = split /\./, $meta->{'string'};
+    while ( @parts > 2 ) {
+        my $new_str = join '.', splice @parts, 0, 2;
+        my $m = $cache{ $new_str };
+        unless ( $m ) {
+            my $name = 'a'. ++$i;
+            $name = "a". ++$i while exists $aliases->{ $name };
+            $m = {
+                name     => $name,
+                string   => $new_str,
+                chain    => [ $meta->{'chain'}[0] ],
+                previous => $meta->{'previous'},
+            };
+            $cache{ $new_str } = $aliases->{ $name } = $m;
+        }
+        shift @{ $meta->{'chain'} };
+        unshift @parts, $m->{'name'};
+        $meta->{'previous'} = $m;
+        $meta->{'string'} = join '.', @parts;
+        $meta = $m;
     }
+};
 
-    my $collection = $self->{'collection'};
+sub merge_joins {
+    my $self = shift;
+    my $tree = shift;
+    %cache = ();
+    $aliases = $tree->{'aliases'};
 
-    my $last_column = pop @chain;
-    my $last_alias = 'main';
+    $merge_joins_cb->( $_ ) foreach values %$aliases;
+    $self->apply_callback_to_tree(
+        $tree->{'conditions'},
+        sub {
+            my $condition = shift;
+            $merge_joins_cb->( $_ ) foreach
+                grep ref $_, map $condition->{$_}, qw(lhs rhs);
+        }
+    );
+}
+}
 
-    my %aliases;
+sub bundle_conditions {
+    my $self = shift;
+    my $tree = shift;
 
-    foreach my $column ( @chain ) {
-        unless ( blessed $column ) {
-            if ( my $tmp = $aliases{ refaddr $column } ) {
-                ($last_alias, $last_column) = @$tmp;
-            } else {
-                ($last_alias, $last_column) = $self->resolve_join( @$column );
-            }
-            next;
+    $self->apply_callback_to_tree(
+        $tree->{'conditions'},
+        sub {
+            my $condition = shift;
+            return if ref $condition->{'rhs'};
+            my $lhs = $condition->{'lhs'};
         }
+    );
+}
+
+sub resolve_join {
+    my $self = shift;
+    my $meta = shift;
+
+    return $meta->{'sql_alias'} if $meta->{'sql_alias'};
+
+    my $collection = $self->{'collection'};
+
+    my $last_alias = 'main';
+    if ( my $prev = $meta->{'previous'} ) {
+        $last_alias = $self->resolve_join( $prev );
+    }
 
+    my @chain = @{ $meta->{'chain'} };
+    while( my $column = shift @chain ) {
         my $name = $column->name;
 
         my $classname = $column->refers_to;
         unless ( $classname ) {
-            die "column '$name' of is not a reference";
+            die "column '$name' is not a reference when there are still items in the chain"
+                if @chain;
+            return $meta->{'sql_alias'} = $last_alias;
         }
 
         if ( UNIVERSAL::isa( $classname, 'Jifty::DBI::Collection' ) ) {
@@ -158,10 +222,11 @@
             die "Column '$name' refers to '$classname' which is not record or collection";
         }
     }
-    return ($last_alias, $last_column);
+    $meta->{'sql_alias'} = $last_alias;
+    return $last_alias;
 }
 
-sub split_condition {
+sub parse_condition {
     my $self = shift;
     my $string = shift;
     my $aliases = shift;
@@ -192,27 +257,32 @@
     my $string = shift;
     my $aliases = shift;
 
-    my @res;
+    my %res = (
+        string   => $string,
+        previous => undef,
+        chain    => [],
+    );
 
     my ($start_from, @names) = split /\./, $string;
     my $item;
     unless ( $start_from ) {
         $item = $self->{'collection'}->new_item;
     } else {
-        my $alias = $aliases->{ $start_from } || die "$start_from alias is not defined";
-        $item = $alias->[-1]->refers_to->new( handle => $self->{'collection'}->_handle );
-        push @res, $alias;
+        my $alias = $aliases->{ $start_from }
+            || die "$start_from alias is not declared in from clause";
+        $res{'previous'} = $alias;
+        $item = $alias->{'chain'}[-1]->refers_to->new( handle => $self->{'collection'}->_handle );
     }
     while ( my $name = shift @names ) {
         my $column = $item->column( $name );
         die "$item has no column '$name'" unless $column;
 
-        push @res, $column;
-        return \@res unless @names;
+        push @{ $res{'chain'} }, $column;
+        return \%res unless @names;
 
         my $classname = $column->refers_to;
         unless ( $classname ) {
-            die "column '$name' of $item is not a reference";
+            die "column '$name' of $item is not a reference to record or collection";
         }
 
         if ( UNIVERSAL::isa( $classname, 'Jifty::DBI::Collection' ) ) {
@@ -226,7 +296,7 @@
         }
     }
 
-    return \@res;
+    return \%res;
 }
 
 sub filter_conditions_tree {
@@ -239,7 +309,7 @@
         next if $skip_next-- > 0;
 
         if ( ref $entry eq 'ARRAY' ) {
-            my $tmp = $self->filter_conditions( $entry, $cb, 1 );
+            my $tmp = $self->filter_conditions_tree( $entry, $cb, 1 );
             if ( !$tmp || (ref $tmp eq 'ARRAY' && !@$tmp) ) {
                 pop @res;
                 $skip_next = 1 unless @res;
@@ -261,5 +331,16 @@
     return \@res;
 }
 
+sub apply_callback_to_tree {
+    my ($self, $tree, $cb) = @_;
+
+    foreach my $entry ( @$tree ) {
+        if ( ref $entry eq 'ARRAY' ) {
+            $self->apply_callback_to_tree( $entry, $cb );
+        } elsif ( ref $entry eq 'HASH' ) {
+            $cb->( $entry );
+        }
+    }
+}
 
 1;


More information about the Jifty-commit mailing list