[Rt-commit] r4294 - in rt/branches/3.7-EXPERIMENTAL: . lib/RT

ruz at bestpractical.com ruz at bestpractical.com
Mon Dec 12 17:03:38 EST 2005


Author: ruz
Date: Mon Dec 12 17:03:37 2005
New Revision: 4294

Added:
   rt/branches/3.7-EXPERIMENTAL/lib/RT/SQL.pm
Modified:
   rt/branches/3.7-EXPERIMENTAL/   (props changed)
Log:


Added: rt/branches/3.7-EXPERIMENTAL/lib/RT/SQL.pm
==============================================================================
--- (empty file)
+++ rt/branches/3.7-EXPERIMENTAL/lib/RT/SQL.pm	Mon Dec 12 17:03:37 2005
@@ -0,0 +1,155 @@
+package RT::SQL;
+
+use strict;
+use warnings;
+
+# States
+use constant VALUE       => 1;
+use constant AGGREG      => 2;
+use constant OP          => 4;
+use constant OPEN_PAREN  => 8;
+use constant CLOSE_PAREN => 16;
+use constant KEYWORD     => 32;
+my @tokens = qw[VALUE AGGREGATOR OPERATOR OPEN_PAREN CLOSE_PAREN KEYWORD];
+
+use Regexp::Common qw /delimited/;
+my $re_aggreg      = qr[(?i:AND|OR)];
+my $re_delim       = qr[$RE{delimited}{-delim=>qq{\'\"}}];
+my $re_value       = qr[\d+|NULL|$re_delim];
+my $re_keyword     = qr[[{}\w\.]+|$re_delim];
+my $re_op          = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short
+my $re_open_paren  = qr[\(];
+my $re_close_paren = qr[\)];
+
+sub ParseToArray {
+    my ($tree, $node, @pnodes);
+    $node = $tree = [];
+
+    my %callback;
+    $callback{'OpenParen'} = sub { push @pnodes, $node; $node = []; push @{ $pnodes[-1] }, $node };
+    $callback{'CloseParen'} = sub { $node = pop @pnodes };
+    $callback{'EntryAggregator'} = sub { push @$node, $_[0] };
+    $callback{'Condition'} = sub { push @$node, { key => $_[0], op => $_[1], value => $_[2] } };
+
+    Parse(shift, \%callback);
+    return $tree;
+}
+
+sub Parse {
+    my ($string, $cb) = @_;
+    $string = '' unless defined $string;
+
+    my $want = KEYWORD | OPEN_PAREN;
+    my $last = 0;
+
+    my $depth = 0;
+    my ($key,$op,$value) = ("","","");
+
+    # order of matches in the RE is important.. op should come early,
+    # because it has spaces in it.    otherwise "NOT LIKE" might be parsed
+    # as a keyword or value.
+
+    while ($string =~ /(
+                        $re_aggreg
+                        |$re_op
+                        |$re_keyword
+                        |$re_value
+                        |$re_open_paren
+                        |$re_close_paren
+                       )/iogx )
+    {
+        my $match = $1;
+
+        # Highest priority is last
+        my $current = 0;
+        $current = OP          if ($want & OP)          && $match =~ /^$re_op$/io;
+        $current = VALUE       if ($want & VALUE)       && $match =~ /^$re_value$/io;
+        $current = KEYWORD     if ($want & KEYWORD)     && $match =~ /^$re_keyword$/io;
+        $current = AGGREG      if ($want & AGGREG)      && $match =~ /^$re_aggreg$/io;
+        $current = OPEN_PAREN  if ($want & OPEN_PAREN)  && $match =~ /^$re_open_paren$/io;
+        $current = CLOSE_PAREN if ($want & CLOSE_PAREN) && $match =~ /^$re_close_paren$/io;
+
+
+        unless ($current && $want & $current) {
+            my $tmp = substr($string, 0, pos($string)- length($match));
+            $tmp .= '>'. $match .'<--here'. substr($string, pos($string));
+            die "Wrong query, expecting a ", _BitmaskToString($want), " in '$tmp'";
+        }
+
+        # State Machine:
+
+        # Parens are highest priority
+        if ( $current & OPEN_PAREN ) {
+            $cb->{'OpenParen'}->();
+            $depth++;
+            $want = KEYWORD | OPEN_PAREN;
+        }
+        elsif ( $current & CLOSE_PAREN ) {
+            $cb->{'CloseParen'}->();
+            $depth--;
+            $want = AGGREG;
+            $want |= CLOSE_PAREN if $depth;
+        }
+        elsif ( $current & AGGREG ) {
+            $cb->{'EntryAggregator'}->( $match );
+            $want = KEYWORD | OPEN_PAREN;
+        }
+        elsif ( $current & KEYWORD ) {
+            $key = $match;
+            $want = OP;
+        }
+        elsif ( $current & OP ) {
+            $op = $match;
+            $want = VALUE;
+        }
+        elsif ( $current & VALUE ) {
+            $value = $match;
+
+            # Remove surrounding quotes and unescape escaped
+            # characters from $key, $match
+            for ( $key, $value ) {
+                if ( /$re_delim/o ) {
+                    substr($_,0,1) = "";
+                    substr($_,-1,1) = "";
+                }
+                s!\\(.)!$1!g;
+            }
+
+            $cb->{'Condition'}->( $key, $op, $value );
+
+            ($key,$op,$value) = ("","","");
+            $want = AGGREG;
+            $want |= CLOSE_PAREN if $depth;
+        } else {
+            die "Query parser is lost";
+        }
+
+        $last = $current;
+    } # while
+
+    unless( !$last || $last & (CLOSE_PAREN | VALUE) ) {
+        die "Incomplete query, last element (",
+            _BitmaskToString($last),
+            ") is not CLOSE_PAREN or VALUE in '$string'";
+    }
+
+    if( $depth ) {
+        die "Incomplete query, $depth paren(s) isn't closed in '$string'";
+    }
+}
+
+sub _BitmaskToString {
+    my $mask = shift;
+
+    my @res;
+    for( my $i = 0; $i<@tokens; $i++ ) {
+        next unless $mask & (1<<$i);
+        push @res, $tokens[$i];
+    }
+
+    my $tmp = join ', ', splice @res, 0, -1;
+    unshift @res, $tmp if $tmp;
+    return join ' or ', @res;
+}
+
+1;


More information about the Rt-commit mailing list