From 945721f48f74d5cfffef7c7cf3a3d6bc2521f5dd Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 15 Jul 2003 13:16:32 +0000 Subject: import of rt 3.0.4 --- rt/lib/RT/Tickets_Overlay_SQL.pm | 382 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 rt/lib/RT/Tickets_Overlay_SQL.pm (limited to 'rt/lib/RT/Tickets_Overlay_SQL.pm') diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm new file mode 100644 index 000000000..d78a56db3 --- /dev/null +++ b/rt/lib/RT/Tickets_Overlay_SQL.pm @@ -0,0 +1,382 @@ +# BEGIN LICENSE BLOCK +# +# Copyright (c) 1996-2003 Jesse Vincent +# +# (Except where explictly superceded by other copyright notices) +# +# This work is made available to you under the terms of Version 2 of +# the GNU General Public License. A copy of that license should have +# been provided with this software, but in any event can be snarfed +# from www.gnu.org. +# +# This work is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# Unless otherwise specified, all modifications, corrections or +# extensions to this work which alter its source code become the +# property of Best Practical Solutions, LLC when submitted for +# inclusion in the work. +# +# +# END LICENSE BLOCK +use strict; +use warnings; + +# Import configuration data from the lexcial scope of __PACKAGE__ (or +# at least where those two Subroutines are defined.) + +my %FIELDS = %{FIELDS()}; +my %dispatch = %{dispatch()}; + +sub _InitSQL { + my $self = shift; + + # How many of these do we actually still use? + + # Private Member Variales (which should get cleaned) + $self->{'_sql_linksc'} = 0; + $self->{'_sql_watchersc'} = 0; + $self->{'_sql_keywordsc'} = 0; + $self->{'_sql_subclause'} = "a"; + $self->{'_sql_first'} = 0; + $self->{'_sql_opstack'} = ['']; + $self->{'_sql_transalias'} = undef; + $self->{'_sql_trattachalias'} = undef; + $self->{'_sql_keywordalias'} = undef; + $self->{'_sql_depth'} = 0; + $self->{'_sql_localdepth'} = 0; + $self->{'_sql_query'} = ''; + $self->{'_sql_looking_at'} = {}; + +} + +sub _SQLLimit { + # All SQL stuff goes into one SB subclause so we can deal with all + # the aggregation + my $this = shift; + $this->SUPER::Limit(@_, + SUBCLAUSE => 'ticketsql'); +} + +# Helpers +sub _OpenParen { + $_[0]->SUPER::_OpenParen( 'ticketsql' ); +} +sub _CloseParen { + $_[0]->SUPER::_CloseParen( 'ticketsql' ); +} + +=head1 SQL Functions + +=cut + +sub _match { + # Case insensitive equality + my ($y,$x) = @_; + return 1 if $x =~ /^$y$/i; + # return 1 if ((lc $x) eq (lc $y)); # Why isnt this equiv? + return 0; +} + +=head2 Robert's Simple SQL Parser + +Documentation In Progress + +The Parser/Tokenizer is a relatively simple state machine that scans through a SQL WHERE clause type string extracting a token at a time (where a token is: + + VALUE -> quoted string or number + AGGREGator -> AND or OR + KEYWORD -> quoted string or single word + OPerator -> =,!=,LIKE,etc.. + PARENthesis -> open or close. + +And that stream of tokens is passed through the "machine" in order to build up a structure that looks like: + + KEY OP VALUE + AND KEY OP VALUE + OR KEY OP VALUE + +That also deals with parenthesis for nesting. (The parentheses are +just handed off the SearchBuilder) + +=cut + +use Regexp::Common qw /delimited/; + +# States +use constant VALUE => 1; +use constant AGGREG => 2; +use constant OP => 4; +use constant PAREN => 8; +use constant KEYWORD => 16; +my @tokens = qw[VALUE AGGREG OP PAREN KEYWORD]; + +my $re_aggreg = qr[(?i:AND|OR)]; +my $re_value = qr[$RE{delimited}{-delim=>qq{\'\"}}|\d+]; +my $re_keyword = qr[$RE{delimited}{-delim=>qq{\'\"}}|(?:\{|\}|\w|\.)+]; +my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short +my $re_paren = qr'\(|\)'; + +sub _parser { + my ($self,$string) = @_; + my $want = KEYWORD | PAREN; + my $last = undef; + + my $depth = 0; + + my ($ea,$key,$op,$value) = ("","","",""); + + while ($string =~ /( + $re_aggreg + |$re_keyword + |$re_value + |$re_op + |$re_paren + )/igx ) { + my $val = $1; + my $current = 0; + + # Highest priority is last + $current = OP if _match($re_op,$val); + $current = VALUE if _match($re_value,$val); + $current = KEYWORD if _match($re_keyword,$val) && ($want & KEYWORD); + $current = AGGREG if _match($re_aggreg,$val); + $current = PAREN if _match($re_paren,$val); + + unless ($current && $want & $current) { + # Error + # FIXME: I will only print out the highest $want value + die "Error near ->$val<- expecting a ", $tokens[((log $want)/(log 2))], " in $string\n"; + } + + # State Machine: + + # Parens are highest priority + if ($current & PAREN) { + if ($val eq "(") { + $depth++; + $self->_OpenParen; + + } else { + $depth--; + $self->_CloseParen; + } + + $want = KEYWORD | PAREN | AGGREG; + } + elsif ( $current & AGGREG ) { + $ea = $val; + $want = KEYWORD | PAREN; + } + elsif ( $current & KEYWORD ) { + $key = $val; + $want = OP; + } + elsif ( $current & OP ) { + $op = $val; + $want = VALUE; + } + elsif ( $current & VALUE ) { + $value = $val; + + # Remove surrounding quotes from $key, $val + # (in future, simplify as for($key,$val) { action on $_ }) + if ($key =~ /$RE{delimited}{-delim=>qq{\'\"}}/) { + substr($key,0,1) = ""; + substr($key,-1,1) = ""; + } + if ($val =~ /$RE{delimited}{-delim=>qq{\'\"}}/) { + substr($val,0,1) = ""; + substr($val,-1,1) = ""; + } + # Unescape escaped characters + $key =~ s!\\(.)!$1!g; + $val =~ s!\\(.)!$1!g; + # print "$ea Key=[$key] op=[$op] val=[$val]\n"; + + + my $subkey; + if ($key =~ /^(.+?)\.(.+)$/) { + $key = $1; + $subkey = $2; + } + + my $class; + my ($stdkey) = grep { /^$key$/i } (keys %FIELDS); + if ($stdkey && exists $FIELDS{$stdkey}) { + $class = $FIELDS{$key}->[0]; + $key = $stdkey; + } + # no longer have a default, since CF's are now a real class, not fallthrough + # fixme: "default class" is not Generic. + + + die "Unknown field: $key" unless $class; + + $self->{_sql_localdepth} = 0; + die "No such dispatch method: $class" + unless exists $dispatch{$class}; + my $sub = $dispatch{$class} || die;; + $sub->( + $self, + $key, + $op, + $val, + SUBCLAUSE => "", # don't need anymore + ENTRYAGGREGATOR => $ea || "", + SUBKEY => $subkey, + ); + + $self->{_sql_looking_at}{lc $key} = 1; + + ($ea,$key,$op,$value) = ("","","",""); + + $want = PAREN | AGGREG; + } else { + die "I'm lost"; + } + + $last = $current; + } # while + + die "Incomplete query" + unless (($want | PAREN) || ($want | KEYWORD)); + + die "Incomplete Query" + unless ($last && ($last | PAREN) || ($last || VALUE)); + + # This will never happen, because the parser will complain + die "Mismatched parentheses" + unless $depth == 0; + +} + + +=head2 ClausesToSQL + +=cut + +sub ClausesToSQL { + my $self = shift; + my $clauses = shift; + my @sql; + + for my $f (keys %{$clauses}) { + my $sql; + my $first = 1; + + # Build SQL from the data hash + for my $data ( @{ $clauses->{$f} } ) { + $sql .= $data->[0] unless $first; $first=0; + $sql .= " '". $data->[2] . "' "; + $sql .= $data->[3] . " "; + $sql .= "'". $data->[4] . "' "; + } + + push @sql, " ( " . $sql . " ) "; + } + + return join("AND",@sql); +} + +=head2 FromSQL + +Convert a RT-SQL string into a set of SearchBuilder restrictions. + +Returns (1, 'Status message') on success and (0, 'Error Message') on +failure. + +=cut + +sub FromSQL { + my ($self,$query) = @_; + + $self->CleanSlate; + $self->_InitSQL(); + return (1,"No Query") unless $query; + + $self->{_sql_query} = $query; + eval { $self->_parser( $query ); }; + $RT::Logger->error( $@ ) if $@; + return(0,$@) if $@; + + # We only want to look at EffectiveId's (mostly) for these searches. + unless (exists $self->{_sql_looking_at}{'effectiveid'}) { + $self->SUPER::Limit( FIELD => 'EffectiveId', + ENTRYAGGREGATOR => 'AND', + OPERATOR => '=', + QUOTEVALUE => 0, + VALUE => 'main.id' + ); #TODO, we shouldn't be hard #coding the tablename to main. + } + # FIXME: Need to bring this logic back in + + # if ($self->_isLimited && (! $self->{'looking_at_effective_id'})) { + # $self->SUPER::Limit( FIELD => 'EffectiveId', + # OPERATOR => '=', + # QUOTEVALUE => 0, + # VALUE => 'main.id'); #TODO, we shouldn't be hard coding the tablename to main. + # } + # --- This is hardcoded above. This comment block can probably go. + # Or, we need to reimplement the looking_at_effective_id toggle. + + # Unless we've explicitly asked to look at a specific Type, we need + # to limit to it. + unless ($self->{looking_at_type}) { + $self->SUPER::Limit( FIELD => 'Type', + OPERATOR => '=', + VALUE => 'ticket'); + } + + # set SB's dirty flag + $self->{'must_redo_search'} = 1; + $self->{'RecalcTicketLimits'} = 0; + + return (1,"Good Query"); + +} + + +1; + +=pod + +=head2 Exceptions + +Most of the RT code does not use Exceptions (die/eval) but it is used +in the TicketSQL code for simplicity and historical reasons. Lest you +be worried that the dies will trigger user visible errors, all are +trapped via evals. + +99% of the dies fall in subroutines called via FromSQL and then parse. +(This includes all of the _FooLimit routines in Tickets_Overlay.pm.) +The other 1% or so are via _ProcessRestrictions. + +All dies are trapped by eval {}s, and will be logged at the 'error' +log level. The general failure mode is to not display any tickets. + +=head2 General Flow + +Legacy Layer: + + Legacy LimitFoo routines build up a RestrictionsHash + + _ProcessRestrictions converts the Restrictions to Clauses + ([key,op,val,rest]). + + Clauses are converted to RT-SQL (TicketSQL) + +New RT-SQL Layer: + + FromSQL calls the parser + + The parser calls the _FooLimit routines to do DBIx::SearchBuilder + limits. + +And then the normal SearchBuilder/Ticket routines are used for +display/navigation. + +=cut + -- cgit v1.2.1 From 289340780927b5bac2c7604d7317c3063c6dd8cc Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 11 Mar 2004 02:05:38 +0000 Subject: import of rt 3.0.9 --- rt/lib/RT/Tickets_Overlay_SQL.pm | 95 +++++++++++++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 15 deletions(-) (limited to 'rt/lib/RT/Tickets_Overlay_SQL.pm') diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm index d78a56db3..629e6da2d 100644 --- a/rt/lib/RT/Tickets_Overlay_SQL.pm +++ b/rt/lib/RT/Tickets_Overlay_SQL.pm @@ -29,6 +29,10 @@ use warnings; my %FIELDS = %{FIELDS()}; my %dispatch = %{dispatch()}; +my %can_bundle = %{can_bundle()}; + +# Lower Case version of FIELDS, for case insensitivity +my %lcfields = map { ( lc($_) => $_ ) } (keys %FIELDS); sub _InitSQL { my $self = shift; @@ -119,20 +123,57 @@ my $re_keyword = qr[$RE{delimited}{-delim=>qq{\'\"}}|(?:\{|\}|\w|\.)+]; my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short my $re_paren = qr'\(|\)'; +sub _close_bundle +{ + my ($self, @bundle) = @_; + return unless @bundle; + if (@bundle == 1) { + $bundle[0]->{dispatch}->( + $self, + $bundle[0]->{key}, + $bundle[0]->{op}, + $bundle[0]->{val}, + SUBCLAUSE => "", + ENTRYAGGREGATOR => $bundle[0]->{ea}, + SUBKEY => $bundle[0]->{subkey}, + ); + } else { + my @args; + for my $chunk (@bundle) { + push @args, [ + $chunk->{key}, + $chunk->{op}, + $chunk->{val}, + SUBCLAUSE => "", + ENTRYAGGREGATOR => $chunk->{ea}, + SUBKEY => $chunk->{subkey}, + ]; + } + $bundle[0]->{dispatch}->( + $self, \@args, + ); + } +} + sub _parser { my ($self,$string) = @_; my $want = KEYWORD | PAREN; my $last = undef; my $depth = 0; + my @bundle; my ($ea,$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_op |$re_paren )/igx ) { my $val = $1; @@ -156,10 +197,12 @@ sub _parser { # Parens are highest priority if ($current & PAREN) { if ($val eq "(") { + $self->_close_bundle(@bundle); @bundle = (); $depth++; $self->_OpenParen; } else { + $self->_close_bundle(@bundle); @bundle = (); $depth--; $self->_CloseParen; } @@ -204,10 +247,9 @@ sub _parser { } my $class; - my ($stdkey) = grep { /^$key$/i } (keys %FIELDS); - if ($stdkey && exists $FIELDS{$stdkey}) { + if (exists $lcfields{lc $key}) { + $key = $lcfields{lc $key}; $class = $FIELDS{$key}->[0]; - $key = $stdkey; } # no longer have a default, since CF's are now a real class, not fallthrough # fixme: "default class" is not Generic. @@ -219,20 +261,37 @@ sub _parser { die "No such dispatch method: $class" unless exists $dispatch{$class}; my $sub = $dispatch{$class} || die;; - $sub->( - $self, - $key, - $op, - $val, - SUBCLAUSE => "", # don't need anymore - ENTRYAGGREGATOR => $ea || "", - SUBKEY => $subkey, - ); + if ($can_bundle{$class} && + (!@bundle || + ($bundle[-1]->{dispatch} == $sub && + $bundle[-1]->{key} eq $key && + $bundle[-1]->{subkey} eq $subkey))) + { + push @bundle, { + dispatch => $sub, + key => $key, + op => $op, + val => $val, + ea => $ea || "", + subkey => $subkey, + }; + } else { + $self->_close_bundle(@bundle); @bundle = (); + $sub->( + $self, + $key, + $op, + $val, + SUBCLAUSE => "", # don't need anymore + ENTRYAGGREGATOR => $ea || "", + SUBKEY => $subkey, + ); + } $self->{_sql_looking_at}{lc $key} = 1; - + ($ea,$key,$op,$value) = ("","","",""); - + $want = PAREN | AGGREG; } else { die "I'm lost"; @@ -241,6 +300,8 @@ sub _parser { $last = $current; } # while + $self->_close_bundle(@bundle); @bundle = (); + die "Incomplete query" unless (($want | PAREN) || ($want | KEYWORD)); @@ -330,6 +391,10 @@ sub FromSQL { VALUE => 'ticket'); } + # We never ever want to show deleted tickets + $self->SUPER::Limit(FIELD => 'Status' , OPERATOR => '!=', VALUE => 'deleted'); + + # set SB's dirty flag $self->{'must_redo_search'} = 1; $self->{'RecalcTicketLimits'} = 0; -- cgit v1.2.1 From c582e92888b4a5553e1b4e5214cf35217e4a0cf0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 11 Nov 2004 12:13:50 +0000 Subject: import rt 3.0.12 --- rt/lib/RT/Tickets_Overlay_SQL.pm | 125 +++++++++++++++++++++++++++++++++++---- 1 file changed, 115 insertions(+), 10 deletions(-) (limited to 'rt/lib/RT/Tickets_Overlay_SQL.pm') diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm index 629e6da2d..4ba19e606 100644 --- a/rt/lib/RT/Tickets_Overlay_SQL.pm +++ b/rt/lib/RT/Tickets_Overlay_SQL.pm @@ -24,6 +24,8 @@ use strict; use warnings; +use RT::Tickets; + # Import configuration data from the lexcial scope of __PACKAGE__ (or # at least where those two Subroutines are defined.) @@ -46,6 +48,7 @@ sub _InitSQL { $self->{'_sql_subclause'} = "a"; $self->{'_sql_first'} = 0; $self->{'_sql_opstack'} = ['']; + $self->{'_sql_linkalias'} = undef; $self->{'_sql_transalias'} = undef; $self->{'_sql_trattachalias'} = undef; $self->{'_sql_keywordalias'} = undef; @@ -53,6 +56,7 @@ sub _InitSQL { $self->{'_sql_localdepth'} = 0; $self->{'_sql_query'} = ''; $self->{'_sql_looking_at'} = {}; + $self->{'_sql_columns_to_display'} = []; } @@ -60,10 +64,20 @@ sub _SQLLimit { # All SQL stuff goes into one SB subclause so we can deal with all # the aggregation my $this = shift; + $this->SUPER::Limit(@_, SUBCLAUSE => 'ticketsql'); } +sub _SQLJoin { + # All SQL stuff goes into one SB subclause so we can deal with all + # the aggregation + my $this = shift; + + $this->SUPER::Join(@_, + SUBCLAUSE => 'ticketsql'); +} + # Helpers sub _OpenParen { $_[0]->SUPER::_OpenParen( 'ticketsql' ); @@ -169,6 +183,10 @@ sub _parser { # because it has spaces in it. otherwise "NOT LIKE" might be parsed # as a keyword or value. + + + + while ($string =~ /( $re_aggreg |$re_op @@ -180,12 +198,13 @@ sub _parser { my $current = 0; # Highest priority is last - $current = OP if _match($re_op,$val); + $current = OP if _match($re_op,$val) ; $current = VALUE if _match($re_value,$val); $current = KEYWORD if _match($re_keyword,$val) && ($want & KEYWORD); $current = AGGREG if _match($re_aggreg,$val); $current = PAREN if _match($re_paren,$val); + unless ($current && $want & $current) { # Error # FIXME: I will only print out the highest $want value @@ -194,6 +213,8 @@ sub _parser { # State Machine: + #$RT::Logger->debug("We've just found a '$current' called '$val'"); + # Parens are highest priority if ($current & PAREN) { if ($val eq "(") { @@ -209,6 +230,7 @@ sub _parser { $want = KEYWORD | PAREN | AGGREG; } + elsif ( $current & AGGREG ) { $ea = $val; $want = KEYWORD | PAREN; @@ -234,7 +256,7 @@ sub _parser { substr($val,0,1) = ""; substr($val,-1,1) = ""; } - # Unescape escaped characters + # Unescape escaped characters $key =~ s!\\(.)!$1!g; $val =~ s!\\(.)!$1!g; # print "$ea Key=[$key] op=[$op] val=[$val]\n"; @@ -349,20 +371,93 @@ Convert a RT-SQL string into a set of SearchBuilder restrictions. Returns (1, 'Status message') on success and (0, 'Error Message') on failure. + +=begin testing + +use RT::Tickets; + + + +my $tix = RT::Tickets->new($RT::SystemUser); + +my $query = "Status = 'open'"; +my ($id, $msg) = $tix->FromSQL($query); + +ok ($id, $msg); + + +my (@ids, @expectedids); + +my $t = RT::Ticket->new($RT::SystemUser); + +my $string = 'subject/content SQL test'; +ok( $t->Create(Queue => 'General', Subject => $string), "Ticket Created"); + +push @ids, $t->Id; + +my $Message = MIME::Entity->build( + Subject => 'this is my subject', + From => 'jesse@example.com', + Data => [ $string ], + ); + +ok( $t->Create(Queue => 'General', Subject => 'another ticket', MIMEObj => $Message, MemberOf => $ids[0]), "Ticket Created"); + +push @ids, $t->Id; + +$query = ("Subject LIKE '$string' OR Content LIKE '$string'"); + +my ($id, $msg) = $tix->FromSQL($query); + +ok ($id, $msg); + +is ($tix->Count, scalar @ids, "number of returned tickets same as entered"); + +while (my $tick = $tix->Next) { + push @expectedids, $tick->Id; +} + +ok (eq_array(\@ids, \@expectedids), "returned expected tickets"); + +$query = ("id = $ids[0] OR MemberOf = $ids[0]"); + +my ($id, $msg) = $tix->FromSQL($query); + +ok ($id, $msg); + +is ($tix->Count, scalar @ids, "number of returned tickets same as entered"); + +@expectedids = (); +while (my $tick = $tix->Next) { + push @expectedids, $tick->Id; +} + +ok (eq_array(\@ids, \@expectedids), "returned expected tickets"); + +=end testing + + =cut sub FromSQL { my ($self,$query) = @_; $self->CleanSlate; + { + # preserve first_row and show_rows across the CleanSlate + local($self->{'first_row'}, $self->{'show_rows'}); + $self->CleanSlate; + } $self->_InitSQL(); - return (1,"No Query") unless $query; + + return (1,$self->loc("No Query")) unless $query; $self->{_sql_query} = $query; eval { $self->_parser( $query ); }; - $RT::Logger->error( $@ ) if $@; - return(0,$@) if $@; - + if ($@) { + $RT::Logger->error( $@ ); + return(0,$@); + } # We only want to look at EffectiveId's (mostly) for these searches. unless (exists $self->{_sql_looking_at}{'effectiveid'}) { $self->SUPER::Limit( FIELD => 'EffectiveId', @@ -386,9 +481,7 @@ sub FromSQL { # Unless we've explicitly asked to look at a specific Type, we need # to limit to it. unless ($self->{looking_at_type}) { - $self->SUPER::Limit( FIELD => 'Type', - OPERATOR => '=', - VALUE => 'ticket'); + $self->SUPER::Limit( FIELD => 'Type', OPERATOR => '=', VALUE => 'ticket'); } # We never ever want to show deleted tickets @@ -399,10 +492,22 @@ sub FromSQL { $self->{'must_redo_search'} = 1; $self->{'RecalcTicketLimits'} = 0; - return (1,"Good Query"); + return (1,$self->loc("Valid Query")); } +=head2 Query + +Returns the query that this object was initialized with + +=cut + +sub Query { + my $self = shift; + return ($self->{_sql_query}); +} + + 1; -- cgit v1.2.1 From d39d52aac8f38ea9115628039f0df5aa3ac826de Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 3 Dec 2004 20:40:48 +0000 Subject: import rt 3.2.2 --- rt/lib/RT/Tickets_Overlay_SQL.pm | 71 ++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 25 deletions(-) (limited to 'rt/lib/RT/Tickets_Overlay_SQL.pm') diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm index 4ba19e606..a873af2d0 100644 --- a/rt/lib/RT/Tickets_Overlay_SQL.pm +++ b/rt/lib/RT/Tickets_Overlay_SQL.pm @@ -1,8 +1,14 @@ -# BEGIN LICENSE BLOCK +# {{{ BEGIN BPS TAGGED BLOCK # -# Copyright (c) 1996-2003 Jesse Vincent +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# # -# (Except where explictly superceded by other copyright notices) +# (Except where explicitly superseded by other copyright notices) +# +# +# LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have @@ -14,13 +20,29 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# # +# CONTRIBUTION SUBMISSION POLICY: # -# END LICENSE BLOCK +# (The following paragraph is not intended to limit the rights granted +# to you to modify and distribute this software under the terms of +# the GNU General Public License and is only of importance to you if +# you choose to contribute your changes and enhancements to the +# community by submitting them to Best Practical Solutions, LLC.) +# +# By intentionally submitting any modifications, corrections or +# derivatives to this work, or any other work intended for use with +# Request Tracker, to Best Practical Solutions, LLC, you confirm that +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# royalty-free, perpetual, license to use, copy, create derivative +# works based on those contributions, and sublicense and distribute +# those contributions and any derivatives thereof. +# +# }}} END BPS TAGGED BLOCK use strict; use warnings; @@ -61,11 +83,19 @@ sub _InitSQL { } sub _SQLLimit { + my $self = shift; + my %args = (@_); + if ($args{'FIELD'} eq 'EffectiveId') { + $self->{'looking_at_effective_id'} = 1; + } + + if ($args{'FIELD'} eq 'Type') { + $self->{'looking_at_type'} = 1; + } + # All SQL stuff goes into one SB subclause so we can deal with all # the aggregation - my $this = shift; - - $this->SUPER::Limit(@_, + $self->SUPER::Limit(%args, SUBCLAUSE => 'ticketsql'); } @@ -90,14 +120,6 @@ sub _CloseParen { =cut -sub _match { - # Case insensitive equality - my ($y,$x) = @_; - return 1 if $x =~ /^$y$/i; - # return 1 if ((lc $x) eq (lc $y)); # Why isnt this equiv? - return 0; -} - =head2 Robert's Simple SQL Parser Documentation In Progress @@ -198,11 +220,11 @@ sub _parser { my $current = 0; # Highest priority is last - $current = OP if _match($re_op,$val) ; - $current = VALUE if _match($re_value,$val); - $current = KEYWORD if _match($re_keyword,$val) && ($want & KEYWORD); - $current = AGGREG if _match($re_aggreg,$val); - $current = PAREN if _match($re_paren,$val); + $current = OP if $val =~ /^$re_op$/io; + $current = VALUE if $val =~ /^$re_value$/io; + $current = KEYWORD if $val =~ /^$re_keyword$/io && ($want & KEYWORD); + $current = AGGREG if $val =~ /^$re_aggreg$/io; + $current = PAREN if $val =~ /^$re_paren$/io; unless ($current && $want & $current) { @@ -442,7 +464,6 @@ ok (eq_array(\@ids, \@expectedids), "returned expected tickets"); sub FromSQL { my ($self,$query) = @_; - $self->CleanSlate; { # preserve first_row and show_rows across the CleanSlate local($self->{'first_row'}, $self->{'show_rows'}); -- cgit v1.2.1 From d4d0590bef31071e8809ec046717444b95b3f30a Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 15 Oct 2005 09:11:20 +0000 Subject: import rt 3.4.4 --- rt/lib/RT/Tickets_Overlay_SQL.pm | 97 +++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 45 deletions(-) (limited to 'rt/lib/RT/Tickets_Overlay_SQL.pm') diff --git a/rt/lib/RT/Tickets_Overlay_SQL.pm b/rt/lib/RT/Tickets_Overlay_SQL.pm index a873af2d0..677391702 100644 --- a/rt/lib/RT/Tickets_Overlay_SQL.pm +++ b/rt/lib/RT/Tickets_Overlay_SQL.pm @@ -1,8 +1,8 @@ -# {{{ BEGIN BPS TAGGED BLOCK +# BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -42,12 +42,12 @@ # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # -# }}} END BPS TAGGED BLOCK +# END BPS TAGGED BLOCK }}} +package RT::Tickets; + use strict; use warnings; -use RT::Tickets; - # Import configuration data from the lexcial scope of __PACKAGE__ (or # at least where those two Subroutines are defined.) @@ -73,7 +73,7 @@ sub _InitSQL { $self->{'_sql_linkalias'} = undef; $self->{'_sql_transalias'} = undef; $self->{'_sql_trattachalias'} = undef; - $self->{'_sql_keywordalias'} = undef; + $self->{'_sql_object_cf_alias'} = undef; $self->{'_sql_depth'} = 0; $self->{'_sql_localdepth'} = 0; $self->{'_sql_query'} = ''; @@ -149,15 +149,18 @@ use Regexp::Common qw /delimited/; use constant VALUE => 1; use constant AGGREG => 2; use constant OP => 4; -use constant PAREN => 8; -use constant KEYWORD => 16; -my @tokens = qw[VALUE AGGREG OP PAREN KEYWORD]; +use constant OPEN_PAREN => 8; +use constant CLOSE_PAREN => 16; +use constant KEYWORD => 32; +my @tokens = qw[VALUE AGGREG OP OPEN_PAREN CLOSE_PAREN KEYWORD]; my $re_aggreg = qr[(?i:AND|OR)]; -my $re_value = qr[$RE{delimited}{-delim=>qq{\'\"}}|\d+]; -my $re_keyword = qr[$RE{delimited}{-delim=>qq{\'\"}}|(?:\{|\}|\w|\.)+]; +my $re_delim = qr[$RE{delimited}{-delim=>qq{\'\"}}]; +my $re_value = qr[$re_delim|\d+|NULL]; +my $re_keyword = qr[$re_delim|(?:\{|\}|\w|\.)+]; my $re_op = qr[=|!=|>=|<=|>|<|(?i:IS NOT)|(?i:IS)|(?i:NOT LIKE)|(?i:LIKE)]; # long to short -my $re_paren = qr'\(|\)'; +my $re_open_paren = qr'\('; +my $re_close_paren = qr'\)'; sub _close_bundle { @@ -193,7 +196,7 @@ sub _close_bundle sub _parser { my ($self,$string) = @_; - my $want = KEYWORD | PAREN; + my $want = KEYWORD | OPEN_PAREN; my $last = undef; my $depth = 0; @@ -214,17 +217,19 @@ sub _parser { |$re_op |$re_keyword |$re_value - |$re_paren - )/igx ) { + |$re_open_paren + |$re_close_paren + )/iogx ) { my $val = $1; my $current = 0; # Highest priority is last - $current = OP if $val =~ /^$re_op$/io; - $current = VALUE if $val =~ /^$re_value$/io; - $current = KEYWORD if $val =~ /^$re_keyword$/io && ($want & KEYWORD); - $current = AGGREG if $val =~ /^$re_aggreg$/io; - $current = PAREN if $val =~ /^$re_paren$/io; + $current = OP if ($want & OP) && $val =~ /^$re_op$/io; + $current = VALUE if ($want & VALUE) && $val =~ /^$re_value$/io; + $current = KEYWORD if ($want & KEYWORD) && $val =~ /^$re_keyword$/io; + $current = AGGREG if ($want & AGGREG) && $val =~ /^$re_aggreg$/io; + $current = OPEN_PAREN if ($want & OPEN_PAREN) && $val =~ /^$re_open_paren$/io; + $current = CLOSE_PAREN if ($want & CLOSE_PAREN) && $val =~ /^$re_close_paren$/io; unless ($current && $want & $current) { @@ -238,24 +243,23 @@ sub _parser { #$RT::Logger->debug("We've just found a '$current' called '$val'"); # Parens are highest priority - if ($current & PAREN) { - if ($val eq "(") { - $self->_close_bundle(@bundle); @bundle = (); - $depth++; - $self->_OpenParen; + if ($current & OPEN_PAREN) { + $self->_close_bundle(@bundle); @bundle = (); + $depth++; + $self->_OpenParen; - } else { - $self->_close_bundle(@bundle); @bundle = (); - $depth--; - $self->_CloseParen; - } - - $want = KEYWORD | PAREN | AGGREG; + $want = KEYWORD | OPEN_PAREN; } + elsif ( $current & CLOSE_PAREN ) { + $self->_close_bundle(@bundle); @bundle = (); + $depth--; + $self->_CloseParen; + $want = CLOSE_PAREN | AGGREG; + } elsif ( $current & AGGREG ) { $ea = $val; - $want = KEYWORD | PAREN; + $want = KEYWORD | OPEN_PAREN; } elsif ( $current & KEYWORD ) { $key = $val; @@ -270,17 +274,17 @@ sub _parser { # Remove surrounding quotes from $key, $val # (in future, simplify as for($key,$val) { action on $_ }) - if ($key =~ /$RE{delimited}{-delim=>qq{\'\"}}/) { + if ($key =~ /$re_delim/o) { substr($key,0,1) = ""; substr($key,-1,1) = ""; } - if ($val =~ /$RE{delimited}{-delim=>qq{\'\"}}/) { + if ($val =~ /$re_delim/o) { substr($val,0,1) = ""; substr($val,-1,1) = ""; } # Unescape escaped characters - $key =~ s!\\(.)!$1!g; - $val =~ s!\\(.)!$1!g; + $key =~ s!\\(.)!$1!g; + $val =~ s!\\(.)!$1!g; # print "$ea Key=[$key] op=[$op] val=[$val]\n"; @@ -336,7 +340,7 @@ sub _parser { ($ea,$key,$op,$value) = ("","","",""); - $want = PAREN | AGGREG; + $want = CLOSE_PAREN | AGGREG; } else { die "I'm lost"; } @@ -347,10 +351,10 @@ sub _parser { $self->_close_bundle(@bundle); @bundle = (); die "Incomplete query" - unless (($want | PAREN) || ($want | KEYWORD)); + unless (($want | CLOSE_PAREN) || ($want | KEYWORD)); die "Incomplete Query" - unless ($last && ($last | PAREN) || ($last || VALUE)); + unless ($last && ($last | CLOSE_PAREN) || ($last || VALUE)); # This will never happen, because the parser will complain die "Mismatched parentheses" @@ -431,14 +435,13 @@ $query = ("Subject LIKE '$string' OR Content LIKE '$string'"); my ($id, $msg) = $tix->FromSQL($query); + ok ($id, $msg); is ($tix->Count, scalar @ids, "number of returned tickets same as entered"); - while (my $tick = $tix->Next) { push @expectedids, $tick->Id; } - ok (eq_array(\@ids, \@expectedids), "returned expected tickets"); $query = ("id = $ids[0] OR MemberOf = $ids[0]"); @@ -476,7 +479,7 @@ sub FromSQL { $self->{_sql_query} = $query; eval { $self->_parser( $query ); }; if ($@) { - $RT::Logger->error( $@ ); + $RT::Logger->error( "Query error in <<$query>>:\n$@" ); return(0,$@); } # We only want to look at EffectiveId's (mostly) for these searches. @@ -505,8 +508,12 @@ sub FromSQL { $self->SUPER::Limit( FIELD => 'Type', OPERATOR => '=', VALUE => 'ticket'); } - # We never ever want to show deleted tickets - $self->SUPER::Limit(FIELD => 'Status' , OPERATOR => '!=', VALUE => 'deleted'); + # We don't want deleted tickets unless 'allow_deleted_search' is set + unless( $self->{'allow_deleted_search'} ) { + $self->SUPER::Limit(FIELD => 'Status', + OPERATOR => '!=', + VALUE => 'deleted'); + } # set SB's dirty flag -- cgit v1.2.1