X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FSQL%2FAbstract%2FTree.pm;h=f6076ba3881988c6f4ae957097bd23966c76f4dd;hb=1c33db5d0ccacdfc4d6028ec596b0f34a045471c;hp=7b97b296b1698c1dbdcc5ae43e1741aba83d81af;hpb=b3b79607321d406a194b2aac205978d925b398c0;p=dbsrgits%2FSQL-Abstract.git diff --git a/lib/SQL/Abstract/Tree.pm b/lib/SQL/Abstract/Tree.pm index 7b97b29..f6076ba 100644 --- a/lib/SQL/Abstract/Tree.pm +++ b/lib/SQL/Abstract/Tree.pm @@ -40,6 +40,8 @@ my $op_look_behind = '(?: (?<= [\,\s\)\(] ) | \A )'; my $quote_left = qr/[\`\'\"\[]/; my $quote_right = qr/[\`\'\"\]]/; +my $placeholder_re = qr/(?: \? | \$\d+ )/x; + # These SQL keywords always signal end of the current expression (except inside # of a parenthesized subexpression). # Format: A list of strings that will be compiled to extended syntax ie. @@ -61,17 +63,25 @@ my @expression_start_keywords = ( )', 'ON', 'WHERE', - 'VALUES', + '(?: DEFAULT \s+ )? VALUES', 'EXISTS', 'GROUP \s+ BY', 'HAVING', 'ORDER \s+ BY', + 'SKIP', + 'FIRST', 'LIMIT', 'OFFSET', 'FOR', 'UNION', 'INTERSECT', 'EXCEPT', + 'BEGIN \s+ WORK', + 'COMMIT', + 'ROLLBACK \s+ TO \s+ SAVEPOINT', + 'ROLLBACK', + 'SAVEPOINT', + 'RELEASE \s+ SAVEPOINT', 'RETURNING', 'ROW_NUMBER \s* \( \s* \) \s+ OVER', ); @@ -114,6 +124,7 @@ my $all_known_re = join("\n\t|\n", $binary_op_re, "$op_look_behind (?i: AND|OR|NOT ) $op_look_ahead", (map { quotemeta $_ } qw/, ( ) */), + $placeholder_re, ); $all_known_re = qr/$all_known_re/x; @@ -131,7 +142,7 @@ use constant PARSE_RHS => 4; my $expr_term_re = qr/ ^ (?: $expr_start_re | \) ) $/x; my $rhs_term_re = qr/ ^ (?: $expr_term_re | $binary_op_re | (?i: AND | OR | NOT | \, ) ) $/x; -my $func_start_re = qr/^ (?: \? | \$\d+ | \( ) $/x; +my $func_start_re = qr/^ (?: \* | $placeholder_re | \( ) $/x; my %indents = ( select => 0, @@ -143,11 +154,16 @@ my %indents = ( join => 1, 'left join' => 1, on => 2, + having => 0, 'group by' => 0, 'order by' => 0, set => 1, into => 1, values => 1, + limit => 1, + offset => 1, + skip => 1, + first => 1, ); my %profiles = ( @@ -158,31 +174,52 @@ my %profiles = ( indent_amount => 2, newline => "\n", colormap => {}, - indentmap => { %indents }, + indentmap => \%indents, eval { require Term::ANSIColor } ? do { my $c = \&Term::ANSIColor::color; + + my $red = [$c->('red') , $c->('reset')]; + my $cyan = [$c->('cyan') , $c->('reset')]; + my $green = [$c->('green') , $c->('reset')]; + my $yellow = [$c->('yellow') , $c->('reset')]; + my $blue = [$c->('blue') , $c->('reset')]; + my $magenta = [$c->('magenta'), $c->('reset')]; + my $b_o_w = [$c->('black on_white'), $c->('reset')]; ( - placeholder_surround => [$c->('black on_cyan'), $c->('reset')], + placeholder_surround => [q(') . $c->('black on_magenta'), $c->('reset') . q(')], colormap => { - select => [$c->('red'), $c->('reset')], - 'insert into' => [$c->('red'), $c->('reset')], - update => [$c->('red'), $c->('reset')], - 'delete from' => [$c->('red'), $c->('reset')], - - set => [$c->('cyan'), $c->('reset')], - from => [$c->('cyan'), $c->('reset')], - - where => [$c->('green'), $c->('reset')], - values => [$c->('yellow'), $c->('reset')], - - join => [$c->('magenta'), $c->('reset')], - 'left join' => [$c->('magenta'), $c->('reset')], - on => [$c->('blue'), $c->('reset')], - - 'group by' => [$c->('yellow'), $c->('reset')], - 'order by' => [$c->('yellow'), $c->('reset')], + 'begin work' => $b_o_w, + commit => $b_o_w, + rollback => $b_o_w, + savepoint => $b_o_w, + 'rollback to savepoint' => $b_o_w, + 'release savepoint' => $b_o_w, + + select => $red, + 'insert into' => $red, + update => $red, + 'delete from' => $red, + + set => $cyan, + from => $cyan, + + where => $green, + values => $yellow, + + join => $magenta, + 'left join' => $magenta, + on => $blue, + + 'group by' => $yellow, + having => $yellow, + 'order by' => $yellow, + + skip => $green, + first => $green, + limit => $green, + offset => $green, } ); } : (), @@ -194,7 +231,7 @@ my %profiles = ( indent_amount => 2, newline => "\n", colormap => {}, - indentmap => { %indents }, + indentmap => \%indents, }, html => { fill_in_placeholders => 1, @@ -207,17 +244,34 @@ my %profiles = ( 'insert into' => ['' , ''], update => ['' , ''], 'delete from' => ['' , ''], - where => ['' , ''], + + set => ['', ''], from => ['' , ''], + + where => ['' , ''], + values => ['', ''], + join => ['' , ''], + 'left join' => ['',''], on => ['' , ''], + 'group by' => ['', ''], + having => ['', ''], 'order by' => ['', ''], - set => ['', ''], - into => ['', ''], - values => ['', ''], + + skip => ['', ''], + first => ['', ''], + limit => ['', ''], + offset => ['', ''], + + 'begin work' => ['', ''], + commit => ['', ''], + rollback => ['', ''], + savepoint => ['', ''], + 'rollback to savepoint' => ['', ''], + 'release savepoint' => ['', ''], }, - indentmap => { %indents }, + indentmap => \%indents, }, none => { colormap => {}, @@ -230,6 +284,9 @@ sub new { my $args = shift || {}; my $profile = delete $args->{profile} || 'none'; + + die "No such profile '$profile'!" unless exists $profiles{$profile}; + my $data = $merger->merge( $profiles{$profile}, $args ); bless $data, $class @@ -245,7 +302,7 @@ sub parse { defined $token and length $token - and + and $token =~ /\S/ ); } @@ -315,8 +372,8 @@ sub _recurse_parse { elsif ( $token =~ / ^ $expr_start_re $ /x ) { my $op = uc $token; my $right = $self->_recurse_parse($tokens, PARSE_IN_EXPR); - $left = $left ? [ $left, [$op => [$right] ]] - : [ $op => [$right] ]; + $left = $left ? [ $left, [$op => [$right||()] ]] + : [ $op => [$right||()] ]; } # NOT elsif ( $token =~ /^ NOT $/ix ) { @@ -326,6 +383,10 @@ sub _recurse_parse { : [ $op => [$right] ]; } + elsif ( $token =~ $placeholder_re) { + $left = $left ? [ $left, [ PLACEHOLDER => [ $token ] ] ] + : [ PLACEHOLDER => [ $token ] ]; + } # we're now in "unknown token" land - start eating tokens until # we see something familiar else { @@ -370,7 +431,7 @@ sub pad_keyword { $before = $self->newline . $self->indent($depth + $self->indentmap->{lc $keyword}); } $before = '' if $depth == 0 and defined $starters{lc $keyword}; - return [$before, ' ']; + return [$before, '']; } sub indent { ($_[0]->indent_string||'') x ( ( $_[0]->indent_amount || 0 ) * $_[1] ) } @@ -395,10 +456,14 @@ sub fill_in_placeholder { return '?' } +# FIXME - terrible name for a user facing API sub unparse { - my ($self, $tree, $bindargs, $depth) = @_; + my ($self, $tree, $bindargs) = @_; + $self->_unparse($tree, [@{$bindargs||[]}], 0); +} - $depth ||= 0; +sub _unparse { + my ($self, $tree, $bindargs, $depth) = @_; if (not $tree or not @$tree) { return ''; @@ -414,29 +479,33 @@ sub unparse { } if (ref $car) { - return join (' ', map $self->unparse($_, $bindargs, $depth), @$tree); + return join (' ', map $self->_unparse($_, $bindargs, $depth), @$tree); } elsif ($car eq 'LITERAL') { - if ($cdr->[0] eq '?') { - return $self->fill_in_placeholder($bindargs) - } return $cdr->[0]; } + elsif ($car eq 'PLACEHOLDER') { + return $self->fill_in_placeholder($bindargs); + } elsif ($car eq 'PAREN') { - return '(' . - join(' ', - map $self->unparse($_, $bindargs, $depth + 2), @{$cdr}) . - ($self->_is_key($cdr)?( $self->newline||'' ).$self->indent($depth + 1):'') . ') '; + return sprintf ('(%s)', + join (' ', map { $self->_unparse($_, $bindargs, $depth + 2) } @{$cdr} ) + . + ($self->_is_key($cdr) + ? ( $self->newline||'' ) . $self->indent($depth + 1) + : '' + ) + ); } elsif ($car eq 'AND' or $car eq 'OR' or $car =~ / ^ $binary_op_re $ /x ) { - return join (" $car ", map $self->unparse($_, $bindargs, $depth), @{$cdr}); + return join (" $car ", map $self->_unparse($_, $bindargs, $depth), @{$cdr}); } elsif ($car eq 'LIST' ) { - return join (', ', map $self->unparse($_, $bindargs, $depth), @{$cdr}); + return join (', ', map $self->_unparse($_, $bindargs, $depth), @{$cdr}); } else { my ($l, $r) = @{$self->pad_keyword($car, $depth)}; - return sprintf "$l%s %s$r", $self->format_keyword($car), $self->unparse($cdr, $bindargs, $depth); + return sprintf "$l%s %s$r", $self->format_keyword($car), $self->_unparse($cdr, $bindargs, $depth); } }