Produce half decent where clauses
Ash Berlin [Mon, 2 Mar 2009 00:17:40 +0000 (00:17 +0000)]
Makefile.PL
lib/SQL/Abstract.pm
t/001_basic.t

index a32a5fb..b208fbb 100644 (file)
@@ -3,9 +3,10 @@ use warnings;
 
 use inc::Module::Install 0.79;
 
+name 'SQL-Abstract';
 requires 'Moose' => '0.71';
-requires 'MooseX::Method::Signatures' => '0.07';
-requires 'MooseX::Declare' => '0.04';
+requires 'MooseX::Method::Signatures' => '0.10';
+requires 'MooseX::Declare' => '0.07';
 
 test_requires 'Test::More';
 
index 7c89615..4e7fcb8 100644 (file)
@@ -8,7 +8,7 @@ class SQL::Abstract {
   use Data::Dump qw/pp/;
 
   use Moose::Util::TypeConstraints;
-  use MooseX::Types -declare => ['NameSeparator'];
+  use MooseX::Types -declare => [qw/NameSeparator/];
   use MooseX::Types::Moose qw/ArrayRef Str/;
   use MooseX::AttributeHelpers;
 
@@ -25,6 +25,19 @@ class SQL::Abstract {
 
   our $AST_VERSION = '1';
 
+  # Operator precedence for bracketing
+  our %PRIO = (
+    and => 10,
+    or  => 50
+  );
+
+  our %OP_MAP = (
+    '>' => '>',
+    '<' => '<',
+    '==' => '=',
+    '!=' => '!=',
+  );
+
   has name_separator => ( 
     is => 'rw', 
     isa => NameSeparator,
@@ -50,8 +63,8 @@ class SQL::Abstract {
     }
   );
 
-  method generate (ArrayRef $ast) {
-    $self = new $self unless blessed($self);
+  method generate (Object|ClassName $self: ArrayRef $ast) {
+    $self = $self->new unless blessed($self);
 
     local $_ = $ast->[0];
     s/^-/_/ or croak "Unknown type tag '$_'";
@@ -100,24 +113,54 @@ class SQL::Abstract {
 
   method _where(ArrayRef $ast) {
     my (undef, @clauses) = @$ast;
+  
+    $DB::single = 1;
+    return 'WHERE ' . $self->_recurse_where(\@clauses);
+  }
 
-    my @output;
+  method _recurse_where($clauses) {
+    $DB::single = 1;
+
+    my $OP = 'AND';
+    my $prio = $PRIO{and};
+    my $first = $clauses->[0];
 
-    foreach (@clauses) {
+    if (!ref $first && $first =~ /^-(and|or)$/) {
+      $OP = uc($1);
+      $prio = $PRIO{$1};
+      shift @$clauses;
+    }
+
+    my @output;
+    foreach (@$clauses) {
       my $op = $_->[0];
 
       unless (substr($op, 0, 1) eq '-') {
         # A simple comparison op (==, >, etc.)
-        croak "Binary operator $op expects 2 children, got " . $#$_
-          if @{$_} > 3;
-
-        push @output, $self->generate($_->[1]), 
-                      $op,
-                      $self->generate($_->[2]);
+        
+        push @output, $self->_binop(@$_);
+        
+      } elsif ($op =~ /^-(and|or)$/) {
+        my $sub_prio = $PRIO{$1}; 
+
+        if ($sub_prio >= $prio) {
+          push @output, $self->_recurse_where($_);
+        } else {
+          push @output, '(' . $self->_recurse_where($_) . ')';
+        }
+      } else {
+        push @output, $self->generate($_);
       }
     }
 
-    return join(' ', 'WHERE', @output);
+    return wantarray ? @output : join(" $OP ", @output);
+  }
+
+  method _binop($op, $lhs, $rhs) {
+    join (' ', $self->generate($lhs), 
+               $OP_MAP{$op} || croak("Unknown binary operator $op"),
+               $self->generate($rhs)
+    );
   }
 
   method _generic_func(ArrayRef $ast) {
index de18152..0ad697f 100644 (file)
@@ -1,10 +1,11 @@
 use strict;
 use warnings;
 
-use Test::More tests => 5;
+use Test::More tests => 9;
 
 use_ok('SQL::Abstract') or BAIL_OUT( "$@" );
 
+    $DB::single = 1;
 is SQL::Abstract->generate( [ -name => qw/me id/]), "me.id",
   "Simple name generator";
 
@@ -27,3 +28,40 @@ is SQL::Abstract->generate(
       [ '>', [-name => qw/me.id/], [-value => 500 ] ]
   ]
 ), "WHERE me.id > ?", "where clause";
+
+
+is SQL::Abstract->generate(
+  [ -where =>
+      [ '>', [-name => qw/me.id/], [-value => 500 ] ],
+      [ '==', [-name => qw/me.name/], [-value => '200' ] ]
+  ]
+), "WHERE me.id > ? AND me.name = ?", "where clause";
+
+
+is SQL::Abstract->generate(
+  [ -where =>  -or =>
+      [ '>', [-name => qw/me.id/], [-value => 500 ] ],
+      [ '==', [-name => qw/me.name/], [-value => '200' ] ],
+  ]
+), "WHERE me.id > ? OR me.name = ?", "where clause";
+
+
+is SQL::Abstract->generate(
+  [ -where =>  -or =>
+      [ '>', [-name => qw/me.id/], [-value => 500 ] ],
+      [ -or => 
+        [ '==', [-name => qw/me.name/], [-value => '200' ] ],
+        [ '==', [-name => qw/me.name/], [-value => '100' ] ]
+      ]
+  ]
+), "WHERE me.id > ? OR me.name = ? OR me.name = ?", "where clause";
+
+is SQL::Abstract->generate(
+  [ -where =>  -or =>
+      [ '==', [-name => qw/me.id/], [-value => 500 ] ],
+      [ -and => 
+        [ '>', [-name => qw/me.name/], [-value => '200' ] ],
+        [ '<', [-name => qw/me.name/], [-value => '100' ] ]
+      ]
+  ]
+), "WHERE me.id = ? OR (me.name > ? AND me.name < ?)", "where clause";