File Coverage

blib/lib/SQL/YASP.pm
Criterion Covered Total %
statement 339 976 34.7
branch 64 474 13.5
condition 32 207 15.4
subroutine 72 122 59.0
pod 10 28 35.7
total 517 1807 28.6


line stmt bran cond sub pod time code
1             package SQL::YASP;
2 1     1   669 use Carp 'croak';
  1         1  
  1         59  
3 1     1   4 use strict;
  1         1  
  1         21  
4 1     1   561 use Tie::IxHash;
  1         3739  
  1         26  
5 1     1   7 use Exporter;
  1         1  
  1         41  
6              
7             # debug tools
8             # use Debug::ShowStuff ':all';
9             # use Debug::ShowStuff::ShowVar;
10              
11             # documentation at end of file
12              
13             # globals
14 1     1   4 use vars qw[@ISA @EXPORT_OK %EXPORT_TAGS %StdDelimiters $defparser $VERSION $nullchar $wineof $err $errstr];
  1         1  
  1         114  
15             $VERSION = '0.12';
16              
17             # export
18             @ISA = 'Exporter';
19             @EXPORT_OK =
20             qw[
21             arr_split get_ixhash comma_split field_set_list
22             ARG_STRING ARG_NONE ARG_RAW ARG_NUMERIC ARG_SENDNULLS
23             OP_BETWEEN OP_LOGICAL OP_ADD OP_MULT OP_EXP OP_MISC
24             ];
25             %EXPORT_TAGS = ('all' => [@EXPORT_OK]);
26              
27              
28             # constants
29 1     1   5 use constant SECTION_RETURN => 0;
  1         2  
  1         87  
30 1     1   5 use constant SECTION_FIELD_SET_LIST => 1;
  1         2  
  1         46  
31 1     1   3 use constant SECTION_COMMA_SPLIT => 2;
  1         1  
  1         32  
32 1     1   3 use constant SECTION_EXPRESSION => 3;
  1         1  
  1         29  
33 1     1   3 use constant SECTION_OBJECT_LIST => 4;
  1         1  
  1         40  
34 1     1   4 use constant SECTION_ARG_LIST => 5;
  1         1  
  1         36  
35 1     1   3 use constant SECTION_SINGLE_WORD => 6;
  1         1  
  1         29  
36 1     1   3 use constant SECTION_TABLE_LIST => 7;
  1         1  
  1         34  
37 1     1   9 use constant SECTION_ORDER_BY => 8;
  1         2  
  1         37  
38 1     1   3 use constant IPOS => 3; # position of the $i argument in sql_split
  1         1  
  1         31  
39              
40             # comparison types
41 1     1   4 use constant CMP_AGNOSTIC => 0;
  1         0  
  1         37  
42 1     1   3 use constant CMP_STRING => 1;
  1         1  
  1         29  
43 1     1   4 use constant CMP_NUMBER => 2;
  1         1  
  1         33  
44              
45             # argument types
46 1     1   3 use constant ARG_STRING => 0;
  1         1  
  1         35  
47 1     1   3 use constant ARG_NONE => 1;
  1         1  
  1         29  
48 1     1   3 use constant ARG_RAW => 2;
  1         1  
  1         36  
49 1     1   3 use constant ARG_NUMERIC => 3;
  1         1  
  1         29  
50 1     1   3 use constant ARG_SENDNULLS => 4;
  1         1  
  1         43  
51              
52             # operator precedence levels
53 1     1   4 use constant OP_BETWEEN => 0;
  1         1  
  1         33  
54 1     1   3 use constant OP_LOGICAL => 1;
  1         2  
  1         30  
55 1     1   3 use constant OP_ADD => 2;
  1         1  
  1         35  
56 1     1   4 use constant OP_MULT => 3;
  1         1  
  1         37  
57 1     1   4 use constant OP_EXP => 4;
  1         1  
  1         29  
58 1     1   7 use constant OP_MISC => 5;
  1         1  
  1         35  
59              
60             # braces around field names
61 1     1   3 use constant FIELD_BRACES_PROHIBIT => 0;
  1         1  
  1         30  
62 1     1   3 use constant FIELD_BRACES_ALLOW => 1;
  1         1  
  1         41  
63 1     1   6 use constant FIELD_BRACES_REQUIRE => 2;
  1         1  
  1         55  
64              
65             # misc constants
66 1     1   4 use constant OPTSPKG => 'SQL::YASP::Opts';
  1         6  
  1         4124  
67              
68              
69             # special characters
70             $nullchar = chr(0);
71             $wineof = chr(26);
72              
73              
74             #------------------------------------------------------------------------------
75             # new
76             # OVERRIDE ME
77             #
78             sub new {
79 1     1 1 5 my ($class) = @_;
80 1         3 my $self = bless({}, $class);
81            
82             # always call after_new just before returning parser object
83 1         4 $self->after_new;
84            
85 1         3 return $self;
86             }
87             #
88             # new
89             #------------------------------------------------------------------------------
90              
91              
92             #------------------------------------------------------------------------------
93             # build_tree
94             # OVERRIDE ME
95             #
96             sub build_tree {
97 1     1 1 2 my ($self, $stmt, $tokens, %opts) = @_;
98 1         2 my ($cmd);
99            
100             # always set $stmt->{'command'}
101 1         3 $cmd = $stmt->{'command'} = shift @$tokens;
102            
103             # create
104 1 0       5 if ($cmd eq 'create')
  0 50       0  
    50          
    0          
    0          
    0          
    0          
105 1 50       4 {$self->tree_create($stmt, @$tokens) or return undef}
106            
107             # select
108             elsif ($cmd eq 'select')
109 0 0       0 {$self->tree_select($stmt, @$tokens) or return undef}
110            
111             # insert
112             elsif ($cmd eq 'insert')
113 0 0       0 {$self->tree_insert($stmt, @$tokens) or return undef}
114            
115             # update
116             elsif ($cmd eq 'update')
117 0 0       0 {$self->tree_update($stmt, @$tokens) or return undef}
118            
119             # delete
120             elsif ($cmd eq 'delete')
121             {$self->tree_delete($stmt, @$tokens) or return undef}
122            
123             # if allow unknown command
124             elsif ($opts{'allow_unknown_command'})
125 0         0 { return undef }
  0         0  
126            
127             # else don't recognize command
128             else
129             {croak "[1] Do not recognize command: [$stmt->{'command'}]"}
130             }
131             #
132             # build_tree
133             #------------------------------------------------------------------------------
134              
135              
136             #------------------------------------------------------------------------------
137             # tree_create
138             #
139             # OVERRIDE ME
140             #
141             sub tree_create {
142 0     0 1 0 my ($self, $stmt, @els) = @_;
143            
144             # hold on to create type
145 0         0 $stmt->{'create_type'} = shift(@els);
146            
147             # create table
148 0 0       0 if ($stmt->{'create_type'} eq 'table')
  0         0  
149             {return $self->tree_create_table($stmt, @els)}
150            
151             # else don't know this type of object
152 0         0 croak "do not know how to create this type of object: $self->{'create_type'}";
153             }
154             #
155             # tree_create
156             #------------------------------------------------------------------------------
157              
158              
159             #------------------------------------------------------------------------------
160             # tree_create_table
161             # OVERRIDE ME
162             #
163             sub tree_create_table {
164 0     0 1 0 my ($self, $stmt, @els) = @_;
165 0         0 my ($fields);
166            
167 0         0 $stmt->{'table_name'} = shift @els;
168 0         0 $stmt->{'fields'} = $fields = get_ixhash();
169            
170             FIELDLOOP:
171 0         0 foreach my $field_def (comma_split(\@els)) {
172 0         0 my @fieldargs = @$field_def;
173 0         0 my ($field_name, $field);
174            
175             # if this is a command, not a field definition
176 0 0 0     0 if (
177             exists($self->{'non_fields'}->{'create'}) &&
178             exists($self->{'non_fields'}->{'create'}->{$fieldargs[0]})
179             ) {
180 0   0     0 $stmt->{'arguments'} ||= [];
181 0         0 push @{$stmt->{'arguments'}}, @fieldargs;
  0         0  
182 0         0 next FIELDLOOP;
183             }
184            
185             # get data type
186 0         0 $field = {};
187 0         0 $field_name = shift @fieldargs;
188 0         0 $field->{'data_type'} = {name=>shift @fieldargs};
189 0         0 $field->{'modifiers'} = [];
190            
191             # add arguments to data type
192 0         0 add_args($field->{'data_type'}, \@fieldargs);
193            
194             # loop through remaining arguments
195 0         0 while (@fieldargs) {
196 0         0 my $arg = shift @fieldargs;
197 0         0 my $setting = {};
198            
199             # if the word is "not", then use the following word
200             # as the arg name
201 0 0       0 if ($arg eq 'not') {
202 0         0 $setting->{'not'} = 1;
203 0         0 $arg = shift @fieldargs;
204             }
205            
206 0         0 add_args($setting, \@fieldargs);
207 0         0 $setting->{'name'} = $arg;
208 0         0 push @{$field->{'modifiers'}}, $setting;
  0         0  
209             }
210            
211             # store in fields hash
212 0         0 $fields->{$field_name} = $field;
213             }
214            
215 0         0 return 1;
216             }
217             #
218             # tree_create_table
219             #------------------------------------------------------------------------------
220              
221              
222             #------------------------------------------------------------------------------
223             # tree_select
224             #
225             # OVERRIDE ME
226             #
227             sub tree_select {
228 1     1 1 2 my ($self, $stmt, @els) = @_;
229 1         1 my ($unset);
230            
231 1         5 $unset = $self->get_sections(
232             $stmt, \@els,
233             'from' => SECTION_TABLE_LIST,
234             'order by' => SECTION_ORDER_BY,
235             'where' => SECTION_EXPRESSION,
236             'having' => SECTION_EXPRESSION,
237             'group by' => SECTION_COMMA_SPLIT,
238             'into' => SECTION_TABLE_LIST,
239             );
240            
241 1 50       3 defined($unset) or return undef;
242            
243 1         4 $stmt->{'fields'} = $self->tree_select_fields($stmt, $unset->{':open'});
244             }
245             #
246             # tree_select
247             #------------------------------------------------------------------------------
248              
249              
250             #------------------------------------------------------------------------------
251             # tree_delete
252             #
253             # OVERRIDE ME
254             #
255             sub tree_delete {
256 0     0 1 0 my ($self, $stmt, @els) = @_;
257 0         0 my ($unset);
258            
259 0         0 $unset = $self->get_sections($stmt, \@els,
260             'from' => SECTION_TABLE_LIST,
261             'where' => SECTION_EXPRESSION,
262             );
263             }
264             #
265             # tree_delete
266             #------------------------------------------------------------------------------
267              
268              
269             #------------------------------------------------------------------------------
270             # tree_insert
271             #
272             # OVERRIDE ME
273             #
274             sub tree_insert {
275 0     0 1 0 my ($self, $stmt, @els) = @_;
276 0         0 my ($unset);
277            
278 0         0 $unset = $self->get_sections($stmt, \@els,
279             'into' => SECTION_RETURN,
280             'values' => SECTION_RETURN,
281             'set' => SECTION_FIELD_SET_LIST,
282             );
283            
284             # into
285 0 0       0 if ($unset->{'into'}) {
286 0         0 $stmt->{'table_name'} = shift @{$unset->{'into'}};
  0         0  
287 0 0       0 get_set_fields($stmt, $unset->{'into'}, $unset)
288             or return undef;
289             }
290             }
291             #
292             # tree_insert
293             #------------------------------------------------------------------------------
294              
295              
296             #------------------------------------------------------------------------------
297             # tree_update
298             #
299             # OVERRIDE ME
300             #
301             sub tree_update {
302 0     0 1 0 my ($self, $stmt, @els) = @_;
303 0         0 my ($unset, $opener);
304            
305 0         0 $unset = $self->get_sections($stmt, \@els,
306             'values' => SECTION_RETURN,
307             'where' => SECTION_EXPRESSION,
308             'set' => SECTION_FIELD_SET_LIST,
309             );
310            
311 0         0 $opener = $unset->{':open'};
312 0         0 $stmt->{'table_name'} = shift @{$opener};
  0         0  
313            
314             # set "set" clause
315 0 0       0 get_set_fields($stmt, $opener, $unset)
316             or return undef;
317             }
318             #
319             # tree_update
320             #------------------------------------------------------------------------------
321              
322              
323             #------------------------------------------------------------------------------
324             # get_set_fields
325             #
326             sub get_set_fields {
327 0     0 0 0 my ($stmt, $fieldlist, $unset) = @_;
328            
329             # if a SET clause wasn't sent, and a VALUES clause was,
330             # set "set" using values
331 0 0 0     0 if ( (! $stmt->{'set'}) && $unset->{'values'} ) {
332 0         0 my (%set, @fields, @exprs, $i);
333            
334 0         0 @fields = comma_split([deref_args($fieldlist)]);
335 0         0 @exprs = comma_split( [deref_args($unset->{'values'})] );
336 0         0 $i = 0;
337            
338 0 0       0 if (@fields != @exprs) {
339 0         0 SQL::YASP::Expr::set_err('invalid syntax: field list and expression list must have same number of elements');
340 0         0 return undef;
341             }
342            
343 0         0 while ($i <= $#fields) {
344 0         0 $set{$fields[$i]->[0]} = SQL::YASP::Expr->new($stmt, $exprs[$i]);
345 0         0 $i++;
346             }
347            
348 0         0 $stmt->{'set'} = \%set;
349             }
350            
351 0         0 return $stmt->{'set'};
352             }
353             #
354             # get_set_fields
355             #------------------------------------------------------------------------------
356              
357              
358             #------------------------------------------------------------------------------
359             # tree_select_fields
360             #
361             sub tree_select_fields {
362 1     1 0 1 my ($self, $stmt, $clause) = @_;
363 1         3 my $cc = ref($self) . '::Expr'; # clause class
364 1         2 my $rv = get_ixhash();
365            
366             # get field list
367 1         11 foreach my $fielddef (arr_split([','], $clause)) {
368 1         2 my @def = @$fielddef;
369            
370             # single field
371 1 50 0     4 if (@def == 1){
    0          
372             # TODO: need to address possibility of format tablename.*
373             # For now we assume that the select is from just one table.
374             #
375             # If that single field is '*', and if we got a table definition hash.
376             #
377 1 50 33     4 if ( ($def[0] eq '*') && $stmt->{'table_definitions'} ) {
  1         3  
378             # Get the name of the first table. See note above
379             # for why we do this little cop-out.
380 0         0 my $tablename = $stmt->{'from'}->{(keys(%{$stmt->{'from'}}))[0]};
  0         0  
381 0         0 my $col_defs = $stmt->{'table_definitions'}->{$tablename}->{'col_defs'};
382            
383 0         0 foreach my $fieldname (keys %{$col_defs})
  0         0  
  0         0  
384             {$rv->{$fieldname} = $cc->new($stmt, $fieldname)}
385             }
386            
387             # else it's just the name of a table
388             else
389             {$rv->{$def[0]} = $cc->new($stmt, @def)}
390             }
391            
392             # else if in format "expression as fieldname"
393 0         0 elsif ( (@def >= 3) && ($def[-2] eq 'as') ) {
394 0         0 my $name = pop @def;
395 0         0 pop @def;
396 0         0 $rv->{$name} = $cc->new($stmt, @def);
397             }
398            
399             # else use entire string as field name
400             else
401             {$rv->{restring(@def)} = $cc->new($stmt, @def)}
402             }
403            
404 1         16 return $rv;
405             }
406             #
407             # tree_select_fields
408             #------------------------------------------------------------------------------
409              
410              
411             ###############################################################################
412             # IT IS NOT RECOMMENDED THAT YOU OVERRIDE ANY OF THE METHODS FROM HERE DOWN #
413             ###############################################################################
414              
415              
416             #------------------------------------------------------------------------------
417             # after_new
418             #
419             sub after_new {
420 1     1 0 1 my ($self) = @_;
421 1         2 my (%quotes, %allops);
422            
423             # set which characters are quotes
424 1   50     12 $self->{'quotes'} ||= ['"', "'"];
425 1         1 $quotes{$_} = 1 for @{$self->{'quotes'}};
  1         8  
426 1         3 $self->{'quotes'} = \%quotes;
427            
428             # tokenizer properties
429 1 50       5 exists($self->{'lukas'}) or $self->{'lukas'} = 1;
430 1 50       3 exists($self->{'type_fix'}) or $self->{'type_fix'} = 1;
431 1 50       4 exists($self->{'perl_regex'}) or $self->{'perl_regex'} = 1;
432 1 50       3 exists($self->{'star_comments'}) or $self->{'star_comments'} = 1;
433 1 50       4 exists($self->{'dash_comments'}) or $self->{'dash_comments'} = 1;
434 1 50       6 exists($self->{'pound_comments'}) or $self->{'pound_comments'} = 1;
435 1 50       4 exists($self->{'!_is_not'}) or $self->{'!_is_not'} = 1;
436 1 50       3 exists($self->{'backslash_escape'}) or $self->{'backslash_escape'} = 1;
437 1 50       5 exists($self->{'dquote_escape'}) or $self->{'dquote_escape'} = 1;
438 1 50       4 exists($self->{'field_braces'}) or $self->{'field_braces'} = FIELD_BRACES_PROHIBIT;
439            
440             # double word tokens
441 1   50     9 $self->{'double_word_tokens'} ||= {
442             primary => {key=>1},
443             current => {date=>1},
444             order => {by=>1},
445             group => {by=>1},
446             };
447            
448             # operators
449 1   50     5 $self->{'ops'} ||= \@SQL::YASP::Expr::dbin;
450            
451             # functions
452 1   50     7 $self->{'functions'} ||= \%SQL::YASP::Expr::dfuncs;
453            
454             # hash of all operators
455 1         2 foreach my $level (@{$self->{'ops'}})
  1         4  
  6         39  
456 6         4 {@allops{keys %{$level}} = ()}
457 1         3 $self->{'allops'} = \%allops;
458            
459             # operator regex
460 1         5 $self->{'opregex'} = join('|', sort {length($b) <=> length($a)} map {$_=quotemeta($_)} keys %allops);
  150         113  
  39         42  
461            
462             # This hash of words indicates words that are not field names,
463             # they are some other type of modifier. This property is mainly
464             # used by 'create table'.
465 1   50     12 $self->{'non_fields'} ||= {
466             create => {
467             constraint => 1,
468             unique => 1,
469             },
470             };
471            
472            
473             #---------------------------------------------------------------
474             # extend Statement and Expr packages if they don't already exist
475             #
476 1 50       4 unless ( (my $class = ref($self)) eq 'SQL::YASP') {
477 0         0 my @isa;
478            
479 0         0 eval "\@isa = \@${class}::Statement::ISA";
480 0 0       0 @isa or eval "\@isa = \@${class}::Statement::ISA = 'SQL::YASP::Statement'";
481 0 0       0 @isa or croak 'did not set @isa';
482            
483 0         0 @isa = ();
484 0         0 eval "\@isa = \@${class}::Expr::ISA";
485 0 0       0 @isa or eval "\@isa = \@${class}::Expr::ISA = 'SQL::YASP::Expr'";
486 0 0       0 @isa or croak 'did not set @isa';
487             }
488             #
489             # extend Statement and Expr packages if they don't already exist
490             #---------------------------------------------------------------
491            
492              
493             }
494             #
495             # after_new
496             #------------------------------------------------------------------------------
497              
498              
499             #------------------------------------------------------------------------------
500             # parse
501             #
502             sub parse {
503 1     1 0 170 my ($self, $sql, %opts) = @_;
504 1         2 my ($rv, @tokens, $carry);
505            
506             # create parser if one wasn't passed
507 1 50       4 unless (ref $self) {
508 1   33     7 $self::defparser ||= $self->new;
509 1         2 $self = $self::defparser;
510             }
511            
512             # instantiate statement object to be returned
513 1         6 $rv = SQL::YASP::Statement->new();
514            
515             # hold on to original SQL if requested to do so
516 1 50       3 $self->{'keep_org_sql'} and $rv->{'org_sql'} = $sql;
517            
518             # remove trailing semicolon
519 1         3 $sql =~ s|\s*\;\s*$||s;
520            
521             # tokenize statement
522 1         3 $carry = {placeholders=>[]};
523 1         4 @tokens = $self->sql_split($sql, $carry);
524            
525             # get the command for this statement
526 1         8 $rv->{'placeholders'} = $carry->{'placeholders'};
527 1         1 $rv->{'placeholder_count'} = @{$carry->{'placeholders'}};
  1         2  
528 1         2 $rv->{'parser'} = $self;
529 1         2 $rv->{'table_definitions'} = $opts{'table_definitions'};
530            
531             # build statement tree
532 1 50       5 $self->build_tree($rv, \@tokens, %opts) or return undef;
533            
534             # return statement object
535 1         4 return $rv;
536             }
537             #
538             # parse
539             #------------------------------------------------------------------------------
540              
541              
542             #------------------------------------------------------------------------------
543             # get_sections
544             #
545             sub get_sections {
546 1     1 1 5 my ($self, $stmt, $els, %opts) = @_;
547 1         6 my @clauses = arr_split([keys %opts], $els, keep_del_front=>1);
548 1         5 my $rv = {};
549            
550             # if the first element is not a recognized command
551 1 50       4 unless (exists $opts{$els->[0]}) {
552 1         2 my $open = shift @clauses;
553 1         3 $rv->{':open'} = $open;
554             }
555            
556             # loop through sections assigning to statement
557             CLAUSELOOP:
558 1         3 foreach my $clause (@clauses) {
559 2         6 my $sname = shift @$clause;
560            
561             # field set list
562 2 50       16 if ($opts{$sname} == SECTION_FIELD_SET_LIST)
  0 50       0  
    100          
    50          
    50          
    50          
    0          
    0          
563 0         0 {$stmt->{$sname} = field_set_list($stmt, $clause)}
564            
565             # single word
566             elsif ($opts{$sname} == SECTION_SINGLE_WORD)
567             {$stmt->{$sname} = $clause->[0]}
568            
569             # from clause
570             # for now, just returns a single hash element
571 0         0 elsif ($opts{$sname} == SECTION_TABLE_LIST) {
572 1         4 my $tdefs = get_ixhash();
573            
574 1         5 foreach my $table_def (comma_split($clause)) {
575 1         2 my ($key, $name);
576            
577             # check for expression-as-table, which is
578             # out of scope
579 1         3 foreach my $def (@$table_def)
580 1 50       4 { ref($def) and return undef }
581            
582             # get name
583 1         2 $name = lc(shift(@{$table_def}));
  1         5  
584            
585             # if alias
586 1 50       2 if (@{$table_def})
  1         4  
  0         0  
587 1         6 {$key = $table_def->[0]}
588             else
589             {$key = $name}
590            
591 1         9 $tdefs->{$key} = $name;
592             }
593            
594             # default $stmt->{'table_name'} to empty string
595 1         20 $stmt->{'table_name'} = '';
596            
597             # if 'from' clause contains exactly one table,
598             # put that single table into the {'table_name'} element
599 1 50       5 if (keys(%$tdefs) == 1) {
600 1         22 my ($key) = keys(%$tdefs);
601 1         9 my ($val) = values(%$tdefs);
602            
603 1 50       18 if ($key eq $val) {
604 1         2 $stmt->{'table_name'} = $val;
605             }
606             }
607            
608 1         2 $stmt->{$sname} = $tdefs;
609             }
610            
611             # comma delimited list
612             elsif ($opts{$sname} == SECTION_COMMA_SPLIT)
613             {$stmt->{$sname} = comma_split($clause)}
614            
615             # comma delimited list, build into expression objects
616 1         7 elsif ($opts{$sname} == SECTION_ORDER_BY){
617 0         0 my $exprs = comma_split($clause);
618            
619 0         0 foreach my $expr (@$exprs) {
620 0         0 my ($desc);
621            
622 0 0       0 if ($expr->[-1] eq 'desc') {
623 0         0 $desc = 1;
624 0         0 pop @$expr;
625             }
626            
627 0         0 $expr = SQL::YASP::Expr->new($stmt, $expr);
628 0         0 $expr->{'desc'} = $desc;
629             }
630            
631 0         0 $stmt->{$sname} = $exprs;
632             }
633            
634             # expression
635             elsif ($opts{$sname} == SECTION_EXPRESSION)
636 0         0 {$stmt->{$sname} = SQL::YASP::Expr->new($stmt, $clause)}
637            
638             # object list
639             elsif ($opts{$sname} == SECTION_OBJECT_LIST)
640 0         0 {$stmt->{$sname} = object_list($clause)}
641            
642             # argument list
643             elsif ($opts{$sname} == SECTION_ARG_LIST)
644 0         0 {$stmt->{$sname} = comma_split([deref_args($clause)])}
645            
646             # else return
647             else
648             {$rv->{$sname} = $clause}
649             }
650            
651 1         4 return $rv;
652             }
653             #
654             # get_sections
655             #------------------------------------------------------------------------------
656              
657              
658             #------------------------------------------------------------------------------
659             # field_set_list
660             #
661             sub field_set_list {
662 0     0 1 0 my ($stmt, $allsets) = @_;
663 0         0 my $rv = {};
664            
665 0         0 foreach my $set (comma_split($allsets)) {
666 0         0 my ($name, $expr) = arr_split(['='], $set, max=>2);
667             # $rv->{$name->[0]} = $expr;
668 0         0 $rv->{$name->[0]} = SQL::YASP::Expr->new($stmt, $expr);
669             }
670            
671 0         0 return $rv;
672             }
673             #
674             # field_set_list
675             #------------------------------------------------------------------------------
676              
677              
678             #------------------------------------------------------------------------------
679             # add_args
680             #
681             sub add_args {
682 0     0 0 0 my ($field, $arr, %opts) = @_;
683 0         0 my ($args);
684            
685             # early exit
686 0 0       0 @{$arr} or return 0;
  0         0  
687 0 0       0 ref($arr->[0]) or return 0;
688            
689             # default property name for arguments
690 0 0       0 defined($opts{'arg_name'}) or $opts{'arg_name'} = 'arguments';
691            
692             # add arguments property
693 0         0 $args = shift @{$arr};
  0         0  
694 0         0 $field->{$opts{'arg_name'}} = $args;
695 0         0 return 1;
696             }
697             #
698             # add_args
699             #------------------------------------------------------------------------------
700              
701              
702             #------------------------------------------------------------------------------
703             # sql_split
704             #
705             sub sql_split {
706 1     1 0 2 my ($self, $sql, $carry, $i) = @_;
707 1         1 my (@rv, @major, $inquote, $inlinecomment, $instar, $allspaces, @chars, @field, $dtokens, $lastwasnum, $inregex);
708 1         2 my %quotes = %{$self->{'quotes'}};
  1         3  
709 1         2 my $opregex = $self->{'opregex'};
710            
711             # values that are carried through recursions
712 1   50     3 $carry ||= {};
713 1   50     3 $carry->{'placeholders'} ||= [];
714            
715             # split entire string into single characters
716 1 50       14 @chars = ref($sql) ? @$sql : split('', $sql);
717 1 50       3 defined($i) or $i=0;
718            
719             # loop through characters
720             CHARLOOP:
721 1         5 while ($i <= $#chars) {
722 41         30 my $char = $chars[$i++];
723            
724             # if in quote
725 41 50 33     373 if (defined $inquote) {
    50 33        
    50 33        
    50 33        
    50 33        
    50 33        
    50 66        
    50 66        
    50 33        
    50          
    50          
726             # escape next character
727 0 0 0     0 if ( ($char eq '\\') && ($self->{'backslash_escape'}) ) {
    0          
728 0         0 push @field, $char, splice(@chars, $i, 1);
729 0         0 next CHARLOOP;
730             }
731            
732             elsif ($char eq $inquote) {
733             # if the next character is also a quote,
734             # then remove it and don't go out of inquote mode
735 0 0 0     0 if ( (! $inregex) && defined($chars[$i]) && ($chars[$i] eq $inquote) && $self->{'dquote_escape'} )
  0   0     0  
      0        
736             {push @field, splice(@chars, $i, 1)}
737            
738             # else end the quote
739             else {
740 0         0 my ($field);
741 0         0 undef $inquote;
742            
743             # if in regex
744 0 0       0 if ($inregex) {
  0         0  
745 0         0 my @params;
746 0         0 $field = {rx => join('', @field)};
747            
748             # get trailing characters
749 0   0     0 while ( ($i <= $#chars) && ($chars[$i] =~ m|[a-z]|i) )
  0         0  
750             {push @params, splice@chars, $i, 1}
751            
752 0         0 $field->{'params'} = join('', @params);
753 0         0 undef $inregex;
754             }
755            
756             # else regular quote
757             else
758             {$field = joinfield(@field, $char)}
759            
760 0         0 push @major, $field;
761 0         0 @field = ();
762 0         0 next CHARLOOP;
763             }
764             }
765             }
766            
767             # in line comment
768             elsif ($inlinecomment) {
769 0 0       0 $char =~ m|[\n\r]| or next CHARLOOP;
770 0         0 undef $inlinecomment;
771             }
772            
773             # in star comment
774             elsif ($instar) {
775 0 0 0     0 if ( ($char eq '*') && ($chars[$i] eq '/')) {
776 0         0 splice(@chars, $i, 1);
777 0         0 undef $instar;
778             }
779            
780 0         0 next CHARLOOP;
781             }
782            
783             # if char is a -, and the next character is also a -
784             elsif ( ($char eq '-') && $self->{'dash_comments'} && ($chars[$i] eq '-')) {
785 0         0 splice(@chars, $i, 1);
786 0         0 $inlinecomment = 1;
787 0         0 next CHARLOOP;
788             }
789            
790             # if char is a #
791             elsif ( ($char eq '#') && $self->{'pound_comments'}) {
792 0         0 $inlinecomment = 1;
793 0         0 next CHARLOOP;
794             }
795            
796             # opening /* comment
797             elsif ( ($char eq '/') && $self->{'star_comments'} && ($chars[$i] eq '*')) {
798 0         0 splice(@chars, $i, 1);
799 0         0 $instar = 1;
800 0         0 next CHARLOOP;
801             }
802            
803             # square brace
804             elsif ( $self->{'field_braces'} && ($char eq '[') ) {
805 0         0 push @major, joinfield(@field);
806 0         0 @field = ();
807 0         0 $inquote = ']';
808             }
809            
810             # quote
811             elsif ($quotes{$char}) {
812 0         0 push @major, joinfield(@field);
813 0         0 @field = ();
814 0         0 $inquote = $char;
815             }
816            
817             # regex
818             elsif ( ($char eq '=') && $self->{'perl_regex'} && $chars[$i] && ($chars[$i] eq '~')) {
819             # purge everything up to here
820 0         0 push @field, $char, splice(@chars, $i, 1);
821 0         0 push @major, joinfield(@field);
822 0         0 @field = ();
823            
824             # remove leading spaces and alphas
825 0   0     0 while ( ($i <= $#chars) && ($chars[$i] =~ m|[\sa-z]|i) )
  0         0  
826             {splice @chars, $i, 1}
827            
828             # get closing character
829 0         0 $inquote = splice @chars, $i, 1;
830 0         0 $inquote =~ tr/\[\{\(/\]\}\)/;
831 0         0 $inregex = 1;
832 0         0 next CHARLOOP;
833             }
834            
835             # opening paren
836 0         0 elsif ($char eq '(') {
837 0         0 push @major, joinfield(@field), [sql_split($self, \@chars, $carry, $i)];
838 0         0 @field = ();
839 0         0 next CHARLOOP;
840             }
841            
842             # else if this is a closing paren
843             elsif ($char eq ')')
844             {last CHARLOOP}
845            
846             # add the character to the field
847 41         80 push @field, $char;
848             }
849            
850             # get last field
851 1         4 push @major, joinfield(@field);
852            
853            
854             # pass position back to caller
855 1         2 $_[IPOS] = $i;
856              
857            
858             #-------------------------------------------------
859             # split by delimiters
860             #
861 1         2 foreach my $el (@major) {
862             # quoted strings and references don't get split
863 1 50 33     8 if (
    50          
    50          
864 0         0 ref($el) ||
865             ($self->{'field_braces'} ? ($el =~ m|^['"\[]|) : ($el =~ m|^['"]|) )
866             )
867 16         29 {push @rv, $el}
868            
869            
870             elsif(length $el)
871 1         187 {push @rv, grep {m|\S|s} split(m/(\s|[\!\?\,]|[\d\.]+|[a-z0-9_]+|$opregex|\S+)\s*/soi, $el);}
872             }
873             #
874             # split by delimiters
875             #-------------------------------------------------
876            
877            
878             #-------------------------------------------------
879             # change placeholders to references
880             # lowercase elements that aren't quoted or references
881             # unquote elements
882             #
883 1         3 foreach my $el (@rv) {
884 8 50 33     24 unless ( ref($el) || ($el =~ m|^['"]|) ) {
885 8 50 33     19 if ($el eq '?') {
  0 50       0  
886 0         0 $el = {
887             placeholder=>1,
888 0         0 index=>scalar @{$carry->{'placeholders'}}
889             };
890            
891 0         0 push @{$carry->{'placeholders'}}, $el;
  0         0  
892             }
893            
894             # alias ! to not
895             elsif ( ($el eq '!') and $self->{'!_is_not'} )
896 8         10 {$el = 'not'}
897             else
898             {$el =~ tr/A-Z/a-z/}
899             }
900             }
901             #
902             # change placeholders to references
903             # lowercase elements that aren't quoted or references
904             #-------------------------------------------------
905            
906            
907             #-------------------------------------------------
908             # compact double word tokens
909             #
910 1         2 $i = 0;
911 1         2 $dtokens = $self->{'double_word_tokens'};
912            
913 1         2 while ($i < $#rv) {
914 7         6 my $el = $rv[$i];
915            
916             # if this is a double_word token
917 7 50       12 unless (ref $el) {
918 7 50       9 if ($dtokens->{$el}) {
919 0 0 0     0 if ( (! ref($rv[$i+1])) && exists $dtokens->{$el}->{$rv[$i+1]} )
  0         0  
920             {$rv[$i] = $rv[$i] . ' ' . splice(@rv, $i+1, 1)}
921             }
922             }
923            
924 7         14 $i++;
925             }
926             #
927             # compact double word tokens
928             #-------------------------------------------------
929            
930            
931             # unquote if necessary
932             #if ($opts{'unquote'}) {
933             # foreach my $el (@rv)
934             # {$el = unquote($el)}
935             #}
936            
937             # remove empty elements
938 1         2 @rv = grep {length($_)} @rv;
  8         10  
939            
940            
941 1         8 return @rv;
942             }
943             #
944             # sql_split
945             #------------------------------------------------------------------------------
946              
947              
948             #------------------------------------------------------------------------------
949             # misc short subs
950             #
951             sub joinfield {
952 1     1 0 5 my($val) = join('', @_);
953 1         6 $val =~ s|^\s+||s;
954 1         5 $val =~ s|\s+$||s;
955 1         2 return $val;
956             }
957              
958             # this could probably be done a lot more efficiently
959             sub unquote {
960 0     0 0 0 my ($rv) = @_;
961            
962             # remove outer quotes
963 0 0       0 if ($rv =~ s|^'(.*)'$|$1|s)
  0 0       0  
964 0         0 {$rv =~ s|''|'|sg}
965             elsif ($rv =~ s|^"(.*)"$|$1|s)
966             {$rv =~ s|""|"|sg}
967            
968             # escapes
969 0         0 my @sets = split m|(\\.)|, $rv;
970            
971 0         0 grep {
972 0         0 s|\\0|$nullchar|o;
973 0         0 s|\\z|$wineof|o;
974 0         0 s|\\t|\t|;
975 0         0 s|\\r|\r|;
976 0         0 s|\\n|\n|;
977 0         0 s|\\b|\b|;
978 0         0 s|\\(.)|$1|;
979             } @sets;
980            
981 0         0 return join('', @sets);
982             }
983              
984             sub count_ops {
985             return
986 0     0 0 0 keys(%SQL::YASP::Expr::bin) +
987             keys(%SQL::YASP::Expr::functions);
988             }
989              
990             sub default_ops {
991 0     0 0 0 return [@SQL::YASP::Expr::dbin];
992             }
993              
994             sub default_functions {
995 0     0 0 0 return {%SQL::YASP::Expr::dfuncs};
996             }
997              
998              
999             #
1000             # misc short subs
1001             #------------------------------------------------------------------------------
1002              
1003              
1004             #------------------------------------------------------------------------------
1005             # arr_split
1006             #
1007             # splits an array into an array of arrays
1008             #
1009             sub arr_split {
1010 3     3 0 7 my ($del_arr, $outer, %opts) = @_;
1011 3         3 my (@current, @rv, %dels, $firstdone);
1012 3 50       6 ref($outer) or return $outer;
1013 3 50       7 $opts{'max'} and $opts{'max'}--;
1014 3         6 @dels{@$del_arr} = ();
1015            
1016 3         5 foreach my $el (@$outer) {
1017              
1018 9 50 66     39 if ( (! ref $el) && exists($dels{$el}) && ($opts{'max'} ? @rv<$opts{'max'} : 1) ) {
  7 100 66     9  
1019 2 50       5 if ($opts{'keep_del_back'})
  0         0  
1020             {push @current, $el}
1021            
1022 2 50 66     7 if ($firstdone || @current)
  2         4  
1023             {push @rv, [@current]}
1024 2         1 $firstdone = 1;
1025            
1026 2         3 @current = ();
1027            
1028 2 50       4 if ($opts{'keep_del_front'})
  2         3  
1029             {push @current, $el}
1030             }
1031             else
1032             {push @current, $el}
1033             }
1034            
1035             # add last element
1036 3         8 push @rv, [@current];
1037            
1038 3 50       16 wantarray and return @rv;
1039 0         0 return \@rv;
1040             }
1041              
1042             # comma_split
1043 1     1 0 3 sub comma_split {arr_split([','], @_)}
1044              
1045             #
1046             # arr_split
1047             #------------------------------------------------------------------------------
1048              
1049              
1050             #------------------------------------------------------------------------------
1051             # object_list
1052             #
1053             # used for situations where the argument list is a comma delimited
1054             # list of single objects, e.g. table names
1055             #
1056             sub object_list {
1057 0     0 0 0 my @list = deref_args($_[0]);
1058            
1059 0         0 my @rv = grep {$_ ne ','} @list;
  0         0  
1060 0 0       0 wantarray and return @rv;
1061 0         0 return \@rv;
1062             }
1063             #
1064             # object_list
1065             #------------------------------------------------------------------------------
1066              
1067              
1068             #------------------------------------------------------------------------------
1069             # get_ixhash
1070             #
1071             sub get_ixhash {
1072 2     2 0 3 my(%hash);
1073 2 50       12 tie(%hash, 'Tie::IxHash')
1074             or die "unable to tie hash: $!";
1075 2         37 return \%hash;
1076             }
1077             #
1078             # get_ixhash
1079             #------------------------------------------------------------------------------
1080              
1081              
1082             #------------------------------------------------------------------------------
1083             # deref_args
1084             #
1085             sub deref_args {
1086 2     2 0 3 my @args = @_;
1087            
1088             # dereference arguments
1089 2   100     17 while ( (@args == 1) && (UNIVERSAL::isa($args[0], 'ARRAY')) )
  1         5  
1090 1         2 {@args = @{$args[0]}}
1091            
1092 2         7 return @args;
1093             }
1094             #
1095             # deref_args
1096             #------------------------------------------------------------------------------
1097              
1098              
1099             #------------------------------------------------------------------------------
1100             # restring
1101             #
1102             sub restring {
1103 0     0 0 0 my @args = deref_args(@_);
1104 0         0 my (@rv);
1105            
1106             # loop through arguments
1107 0         0 foreach my $arg (@args) {
1108 0 0       0 if (ref $arg) {
1109             # if the arg is a placeholder
1110 0 0 0     0 if (UNIVERSAL::isa($arg, 'HASH') && $arg->{'placeholder'})
  0         0  
1111 0         0 {push @rv, ' ?'}
1112             else
1113             {push @rv, '(', restring($arg), ')'}
1114             }
1115            
1116             else {
1117 0 0 0     0 if (@rv && ($arg ne ',') )
  0         0  
1118             {push @rv, ' '}
1119 0         0 push @rv, $arg;
1120             }
1121             }
1122            
1123 0         0 return join('', @rv);
1124             }
1125             #
1126             # restring
1127             #------------------------------------------------------------------------------
1128              
1129              
1130             # optsref
1131             # turns option hash into anonymous hash
1132 0 0   0 0 0 sub optsref{return ref($_[0]) ? $_[0] : {@_}}
1133              
1134              
1135              
1136             ###############################################################################
1137             # SQL::YASP::Statement
1138             #
1139             package SQL::YASP::Statement;
1140 1     1   8 use strict;
  1         1  
  1         38  
1141 1     1   5 use Carp 'croak';
  1         17  
  1         248  
1142              
1143             #------------------------------------------------------------------------------
1144             # new
1145             #
1146             sub new {
1147 1     1   1 my ($class, $sql) = @_;
1148            
1149 1 50       3 if (defined $sql)
  0         0  
1150             {return SQL::YASP->parse($sql)}
1151            
1152 1         3 return bless({}, $class);
1153             }
1154             #
1155             # new
1156             #------------------------------------------------------------------------------
1157              
1158              
1159             #------------------------------------------------------------------------------
1160             # select_fields
1161             #
1162             sub select_fields {
1163 0     0   0 my ($self, %opts) = @_;
1164 0         0 my $rv = {};
1165 0         0 my $sendopts = {%opts};
1166 0         0 delete $sendopts->{'set'};
1167            
1168             # error checking
1169 0 0       0 $opts{'db_record'} or croak 'select_fields requires a db_record argument';
1170            
1171             # loop through fields
1172 0         0 while (my($n, $v) = each(%{$self->{'fields'}})) {
  0         0  
1173 0 0       0 if ($n eq '*') {
1174 0         0 while (my($on, $ov) = each(%{$opts{'db_record'}}) )
  0         0  
  0         0  
1175             {$rv->{$on} = $ov}
1176             }
1177            
1178             else {
1179 0         0 my ($val);
1180 0 0       0 $v->evalexpr($sendopts, $val) or return undef;
1181 0         0 $rv->{$n} = $val;
1182             }
1183             }
1184            
1185             # store results
1186 0 0       0 if (exists $opts{'set'}) {
1187 0         0 my $i = 1;
1188            
1189 0         0 while ($i < @_) {
1190 0 0       0 if ($_[$i] eq 'set') {
1191 0         0 $_[$i+1] = $rv;
1192 0         0 return 1;
1193             }
1194            
1195 0         0 $i+=2;
1196             }
1197             }
1198            
1199 0         0 return $rv;
1200             }
1201             #
1202             # select_fields
1203             #------------------------------------------------------------------------------
1204              
1205              
1206             #
1207             # SQL::YASP::Statement
1208             ###############################################################################
1209              
1210              
1211              
1212             ###############################################################################
1213             # SQL::YASP::Expr
1214             #
1215             package SQL::YASP::Expr;
1216 1     1   5 use strict;
  1         0  
  1         75  
1217 1     1   4 use Carp 'croak', 'confess';
  1         1  
  1         43  
1218 1     1   3 use vars qw[@dbin %dfuncs];
  1         1  
  1         50  
1219              
1220             # debug tools
1221             # use Debug::ShowStuff ':all';
1222              
1223             # comparison types
1224 1     1   4 use constant CMP_AGNOSTIC => 0;
  1         2  
  1         51  
1225 1     1   3 use constant CMP_STRING => 1;
  1         4  
  1         32  
1226 1     1   3 use constant CMP_NUMBER => 2;
  1         1  
  1         40  
1227              
1228             # operator precedence levels
1229 1     1   3 use constant OP_BETWEEN => SQL::YASP::OP_BETWEEN;
  1         1  
  1         34  
1230 1     1   3 use constant OP_LOGICAL => SQL::YASP::OP_LOGICAL;
  1         2  
  1         37  
1231 1     1   3 use constant OP_ADD => SQL::YASP::OP_ADD;
  1         1  
  1         46  
1232 1     1   4 use constant OP_MULT => SQL::YASP::OP_MULT;
  1         0  
  1         42  
1233 1     1   4 use constant OP_EXP => SQL::YASP::OP_EXP;
  1         1  
  1         38  
1234 1     1   4 use constant OP_MISC => SQL::YASP::OP_MISC;
  1         1  
  1         38  
1235              
1236             # ARGUMENT TYPES
1237 1     1   4 use constant ARG_STRING => SQL::YASP::ARG_STRING;
  1         1  
  1         54  
1238 1     1   7 use constant ARG_NONE => SQL::YASP::ARG_NONE;
  1         1  
  1         60  
1239 1     1   4 use constant ARG_RAW => SQL::YASP::ARG_RAW;
  1         2  
  1         46  
1240 1     1   4 use constant ARG_NUMERIC => SQL::YASP::ARG_NUMERIC;
  1         1  
  1         41  
1241 1     1   4 use constant ARG_SENDNULLS => SQL::YASP::ARG_SENDNULLS;
  1         1  
  1         37  
1242              
1243             # RETURN TYPES
1244 1     1   3 use constant RV_LOOSE => 0;
  1         1  
  1         29  
1245 1     1   3 use constant RV_BOOL => 1;
  1         2  
  1         33  
1246              
1247             # misc constants
1248 1     1   3 use constant EE_BYVAL => 1;
  1         1  
  1         31  
1249 1     1   3 use constant EE_STARTARGS => 2;
  1         2  
  1         7407  
1250              
1251             # alias some subs from main class
1252 2     2   5 sub deref_args{SQL::YASP::deref_args(@_)}
1253 0     0   0 sub arr_split{SQL::YASP::arr_split(@_)}
1254 0     0   0 sub comma_split{SQL::YASP::comma_split(@_)}
1255 0     0   0 sub unquote{SQL::YASP::unquote(@_)}
1256 0     0   0 sub restring{SQL::YASP::restring(@_)}
1257              
1258              
1259             #------------------------------------------------------------------------------
1260             # new
1261             #
1262             sub new {
1263 2     2   3 my $class = shift;
1264 2         2 my $stmt = shift;
1265 2         4 my $self = bless({}, $class);
1266            
1267 2         8 $self->{'parser'} = $stmt->{'parser'};
1268 2         5 $self->{'expr'} = [deref_args(@_)];
1269            
1270 2         7 return $self;
1271             }
1272             #
1273             # new
1274             #------------------------------------------------------------------------------
1275              
1276              
1277             #------------------------------------------------------------------------------
1278             # evalexpr
1279             #
1280             sub evalexpr {
1281 0     0     my ($setval, $org_args, $opts, @args, %allops, $funcs, @oplevels, $rv, $typefix, $parser, $lukas);
1282            
1283             # get first argument, which is either an array ref or an Expr object
1284 0           $org_args = $_[0];
1285            
1286             # if second arg is a ref
1287             # it's the options hash ref
1288             # and the third is the value to set
1289 0 0         if (ref $_[1]) {
1290 0           $opts = $_[1];
1291 0           $setval = 2;
1292             }
1293            
1294             # get the options, and find out if one of them is the set value
1295             else {
1296 0           $opts = {@_[1..$#_]};
1297            
1298 0 0         if (exists $opts->{'set'}) {
1299 0           my $i = 1;
1300            
1301             SETLOOP:
1302 0           while ($i < @_) {
1303 0 0         if ($_[$i] eq 'set') {
1304 0           $setval = $i+1;
1305 0           last SETLOOP;
1306             }
1307            
1308 0           $i+=2;
1309             }
1310             }
1311             }
1312            
1313             # if first arg is a hash, then this is being done as a method call
1314 0 0         if (UNIVERSAL::isa($org_args, 'HASH')) {
1315 0           $opts->{'exprob'} = $org_args;
1316 0           $opts->{'parser'} = $org_args->{'parser'};
1317 0           $org_args = $org_args->{'expr'};
1318             }
1319            
1320             # get arguments
1321 0 0         if (UNIVERSAL::isa($org_args, 'ARRAY'))
  0 0          
1322 0           {@args = $org_args}
1323             elsif (ref $org_args)
1324 0           {@args = $org_args->{'expr'}}
1325             else
1326             {@args = $org_args}
1327            
1328             # dereference arguments
1329 0           @args = deref_args(@args);
1330            
1331             # get stuff from options
1332 0           $parser = $opts->{'exprob'}->{'parser'};
1333 0           $typefix = $parser->{'type_fix'};
1334 0           $lukas = $parser->{'lukas'};
1335 0           $funcs = $parser->{'functions'};
1336 0           %allops = %{$parser->{'allops'}};
  0            
1337 0           @oplevels = @{$parser->{'ops'}};
  0            
1338            
1339            
1340            
1341             #--------------------------------------------------------------------------
1342             # evaluate expression
1343             #
1344             EVALEXPR:
1345             {
1346             # if expression is zero items long, that's a syntax error
1347 0 0         if (! @args) {
  0            
1348 0           set_err('invalid syntax: no arguments');
1349 0           last EVALEXPR;
1350             }
1351            
1352             # if expression is one item long
1353 0 0         if (@args == 1) {
1354 0           my $arg = $args[0];
1355 0 0         defined($arg) or die 'no $arg';
1356            
1357             # if it's a hash
1358 0 0         if (UNIVERSAL::isa($arg, 'HASH')) {
1359             # placeholder
1360 0 0         if ($arg->{'placeholder'}) {
  0            
1361 0 0 0       if ( $opts->{'params'} && @{$opts->{'params'}} ) {
  0            
1362             # make sure we have a placeholder for this index
1363 0 0         if ($arg->{'index'} > $#{$opts->{'params'}})
  0            
  0            
1364             {set_err('More placeholders than params')}
1365            
1366 0           $rv = $opts->{'params'}->[$arg->{'index'}];
1367             }
1368            
1369             else {
1370 0           set_err('Do not have any params to match placeholders');
1371             }
1372             }
1373            
1374             # else just return it
1375             else
1376             {$rv = $arg}
1377            
1378 0           last EVALEXPR;
1379             }
1380            
1381             # if it's an array: should never reach this point
1382 0 0         if (UNIVERSAL::isa($arg, 'ARRAY'))
  0            
1383             {croak 'got single array ref'}
1384            
1385             # field name with braces
1386 0 0 0       if (
1387             $parser->{'field_braces'} &&
1388             ($arg =~ m|^\[.+\]$|s)
1389             ) {
1390             # if no db record was sent, that's an error
1391 0 0         if (! $opts->{'db_record'}) {
1392 0           set_err('Cannot evaluate field expression w/o database record');
1393 0           last EVALEXPR;
1394             }
1395            
1396             # get field name
1397 0           my $field_name = $arg;
1398 0           $field_name =~ s|^\[(.+)\]$|$1|s;
1399            
1400             # normalize
1401 0 0         if ($parser->{'normalize_fields'}) {
1402 0           $field_name =~ s|^\s+||s;
1403 0           $field_name =~ s|\s+$||s;
1404 0           $field_name =~ s|\s+| |gs;
1405 0           $field_name = lc($field_name);
1406             }
1407            
1408             # if the field is in the database record OR
1409             # if we can assume that any field is in the
1410             # record
1411 0 0 0       if (
1412             $opts->{'assume_fields'} ||
1413             exists($opts->{'db_record'}->{$field_name})
1414             ) {
1415 0           $rv = $opts->{'db_record'}->{$field_name};
1416             }
1417            
1418             # else give error that no such field is found
1419             else {
1420 0           set_err('Do not have field named ' . $field_name);
1421             }
1422            
1423 0           last EVALEXPR;
1424             }
1425            
1426             # function
1427 0 0 0       if ($funcs->{$arg}) {
    0 0        
    0 0        
    0          
    0          
1428 0           $rv = &{$funcs->{$arg}->{'s'}}($opts);
  0            
1429 0           sbool($funcs->{$arg}, $rv);
1430             }
1431            
1432             # field name w/o braces
1433             # TODO: normalize non-braced field names, mainly in terms of upper/lowercase
1434             elsif (
1435             $opts->{'db_record'} &&
1436             ($parser->{'field_braces'} != SQL::YASP::FIELD_BRACES_REQUIRE) &&
1437             exists($opts->{'db_record'}->{$arg})
1438 0           ){
1439 0           $rv = $opts->{'db_record'}->{$arg};
1440             }
1441            
1442             # constant
1443             elsif ($opts->{'const'} && exists($opts->{'const'}->{$arg}))
1444 0           {$rv = $opts->{'const'}->{$arg}}
1445            
1446             # literal expression
1447             elsif ($arg =~ m|^['"]|)
1448 0           {$rv = unquote($arg)}
1449            
1450             # number
1451             elsif (is_numeric($arg))
1452 0           {$rv = $arg + 0}
1453            
1454             # else don't know what it is
1455             else
1456             {set_err('cannot interpret expression: ' . $arg)}
1457            
1458 0           last EVALEXPR;
1459             }
1460            
1461             # evaluate expression based on binary operators
1462             # search for loosest bound first
1463 0           foreach my $bg (@oplevels) {
1464 0           my $i = $#args - 1;
1465            
1466             OPLOOP:
1467 0           while ($i > 0) {
1468 0           my $carg = $args[$i];
1469 0           my ($not);
1470            
1471             # if the current argument is a binary operator in this precedence level
1472 0 0 0       if ( (! ref $carg) && $bg->{$carg} ) {
1473            
1474             # KLUDGE: if this operator is ALSO a function, and if the next
1475             # token back is an operator, then skip this operator
1476             # typical scenerio where this kludge comes into play:
1477             # rank/-2
1478 0 0 0       if (
      0        
1479             $funcs->{$carg} &&
1480             ($i > 1) &&
1481             (exists $allops{$args[$i-1]})
1482             ) {
1483 0           $i--;
1484 0           next OPLOOP;
1485             }
1486            
1487 0           my @left = @args[0..($i-1)];
1488 0   0       my $argtype = $bg->{$carg}->{'args'} || 0;
1489 0   0       my $rettype = $bg->{$carg}->{'rv'} || 0;
1490 0           my $sub = $bg->{$carg}->{'s'};
1491            
1492             # determine if we should reverse the logical sense of the expression
1493 0 0         if ($left[-1] eq 'not') {
1494 0           $not = 1;
1495 0           pop @left;
1496             }
1497            
1498             # ARG_RAW
1499 0 0         if ($argtype == ARG_RAW)
  0            
1500 0           {$rv = &{$sub}($opts, [@left], [@args[($i+1)..$#args]])}
1501            
1502             # else evaluate and send
1503             else {
1504 0           my ($a, $b);
1505            
1506 0 0         evalexpr(\@left, $opts, $a) or last EVALEXPR;
1507 0 0         evalexpr([@args[($i+1)..$#args]], $opts, $b) or last EVALEXPR;
1508            
1509             # if lukas, refuse to send nulls to operators
1510             # that don't handle them
1511 0 0         if ($lukas) {
    0          
1512 0 0 0       unless (
      0        
1513             ($argtype == ARG_SENDNULLS) ||
1514             (defined($a) && defined($b))
1515             ) {
1516 0           undef $rv;
1517 0           last EVALEXPR;
1518             }
1519             }
1520            
1521             # elsif fix_types
1522             elsif ($typefix) {
1523 0 0         if ($argtype == ARG_NUMERIC) {
    0          
1524 0           as_number($a);
1525 0           as_number($b);
1526             }
1527            
1528             elsif ($argtype == ARG_STRING) {
1529 0 0         defined($a) or $a = '';
1530 0 0         defined($b) or $b = '';
1531             }
1532             }
1533            
1534             # call operator subroutine
1535 0           $rv = &{$sub}($opts, $a, $b);
  0            
1536             }
1537            
1538 0 0         ($rettype == RV_BOOL) and $rv = $rv ?1:0;
    0          
1539 0 0         $not and $rv = lnot($rv);
1540 0           last EVALEXPR;
1541             }
1542            
1543 0           $i--;
1544             }
1545             }
1546            
1547             # if the first arg is a function name
1548 0 0         if (my $function = $funcs->{$args[0]}) {
1549 0   0       my $argtype = $function->{'args'} || 0;
1550            
1551             # no arguments
1552 0 0         if ($argtype == ARG_NONE) {
1553 0           $rv = &{$function->{'s'}}($opts);
  0            
1554 0           last EVALEXPR;
1555             }
1556            
1557             # remove first argument (which is the function name)
1558             # and deref the rest
1559 0           shift @args;
1560 0           @args = deref_args(@args);
1561            
1562             # send arguments raw
1563 0 0         if ($argtype == ARG_RAW) {
1564 0           $rv = &{$function->{'s'}}($opts, @args);
  0            
1565 0           last EVALEXPR;
1566             }
1567            
1568             # split on commas
1569 0           @args = comma_split(\@args);
1570            
1571             # evaluate arguments
1572 0 0         foreach my $arg (@args)
  0            
1573             {evalexpr($arg, $opts, $arg) or last EVALEXPR}
1574            
1575             # evaluate the arguments
1576 0 0         if (! $argtype) {
    0          
    0          
1577 0 0         $typefix and grep {defined($_) or $_ = ''} @args;
  0 0          
1578 0           $rv = &{$function->{'s'}}($opts, @args);
  0            
1579             }
1580            
1581             # same as ARG_STRING, but let undef be undef
1582             elsif ($argtype == ARG_SENDNULLS) {
1583 0           $rv = &{$function->{'s'}}($opts, @args);
  0            
1584             }
1585            
1586             # same as ARG_STRING, but numify everything
1587 0           elsif ($argtype == ARG_NUMERIC) {
1588 0 0         $typefix and grep {as_number($_)} @args;
  0            
1589 0           $rv = &{$function->{'s'}}($opts, @args);
  0            
1590             }
1591            
1592             # else don't know argument type
1593             else
1594             {croak 'do not know argument type: ' . $argtype}
1595            
1596            
1597 0           sbool($function, $rv);
1598 0           last EVALEXPR;
1599             }
1600            
1601 0           set_err('could not evaluate expression: ' . restring(@args));
1602 0           last EVALEXPR;
1603             }
1604             #
1605             # evaluate expression
1606             #--------------------------------------------------------------------------
1607            
1608            
1609             # if error, return undef
1610 0 0         if ($SQL::YASP::err) {
1611 0 0         if (! $setval)
  0            
1612             {croak 'SQL error: ' . $SQL::YASP::errstr}
1613 0           $_[$setval] = undef;
1614 0           return undef;
1615             }
1616            
1617             # set byval
1618 0 0         if ($setval) {
1619 0           $_[$setval] = $rv;
1620 0           return 1;
1621             }
1622            
1623             # return the value
1624 0           return $rv;
1625             }
1626             #
1627             # evalexpr
1628             #------------------------------------------------------------------------------
1629              
1630              
1631             #------------------------------------------------------------------------------
1632             # comparetype
1633             #
1634             sub comparetype {
1635 0     0     my ($self, %opts) = @_;
1636            
1637             # quick exit
1638 0 0         exists($self->{'comparetype'}) and return $self->{'comparetype'};
1639            
1640 0           my ($args_ref, $defs);
1641 0           my ($parser, $typefix, $lukas, $funcs, %allops, @oplevels, @args);
1642            
1643             # dereference arguments
1644 0   0       $args_ref = $opts{'args'} || $self->{'expr'};
1645 0           @args = deref_args($args_ref);
1646            
1647             # get field definitions
1648 0 0         $defs = $opts{'defs'} or croak 'did not get field definitions';
1649            
1650             # get stuff from options
1651 0           $parser = $self->{'parser'};
1652 0           $typefix = $parser->{'type_fix'};
1653 0           $funcs = $parser->{'functions'};
1654 0           %allops = %{$parser->{'allops'}};
  0            
1655 0           @oplevels = @{$parser->{'ops'}};
  0            
1656            
1657            
1658             # if expression is zero items long, that's a syntax error
1659 0 0         if (! @args) {
1660 0           set_err('invalid syntax: no arguments');
1661 0           last EVALEXPR;
1662             }
1663            
1664             # if expression is one item long
1665 0 0         if (@args == 1){
1666 0           my $arg = $args[0];
1667 0 0         defined($arg) or die 'no $arg';
1668            
1669             # if it's a hash
1670 0 0         if (UNIVERSAL::isa($arg, 'HASH')) {
1671 0           return CMP_AGNOSTIC;
1672             }
1673            
1674             # if it's an array: should never reach this point
1675 0 0         if (UNIVERSAL::isa($arg, 'ARRAY'))
  0            
1676             {croak 'got single array ref'}
1677            
1678             # function
1679 0 0 0       if ($funcs->{$arg}) {
    0          
    0          
    0          
    0          
1680 0           my $func = $funcs->{$arg};
1681            
1682 0 0         defined($func->{'c'}) and return $func->{'c'};
1683 0           return CMP_STRING;
1684             }
1685            
1686             # field name
1687             elsif ( exists $defs->{$arg} )
1688 0           { return $defs->{$arg} }
  0            
1689            
1690             # constant
1691             elsif ($opts{'const'} && exists($opts{'const'}->{$arg}))
1692 0           {return CMP_AGNOSTIC}
1693            
1694             # literal expression
1695             elsif ($arg =~ m|^['"]|)
1696 0           {return CMP_AGNOSTIC}
1697            
1698             # number
1699             elsif (is_numeric($arg))
1700 0           {return CMP_NUMBER}
1701            
1702             # else don't know what it is
1703             else
1704             {set_err('cannot interpret expression: ' . $arg)}
1705            
1706 0           last EVALEXPR;
1707             }
1708            
1709             # evaluate expression based on binary operators
1710             # search for loosest bound first
1711 0           foreach my $bg (@oplevels) {
1712 0           my $i = $#args - 1;
1713            
1714             OPLOOP:
1715 0           while ($i > 0) {
1716 0           my $carg = $args[$i];
1717 0           my ($not);
1718            
1719             # if the current argument is a binary operator in this precedence level
1720 0 0 0       if ( (! ref $carg) && $bg->{$carg} ) {
1721 0           my $subdef = $bg->{$carg};
1722 0 0         defined($subdef->{'c'}) and return $subdef->{'c'};
1723 0           return CMP_STRING;
1724             }
1725            
1726 0           $i--;
1727             }
1728             }
1729            
1730            
1731             # if the first arg is a function name
1732 0 0         if (my $function = $funcs->{$args[0]}) {
1733 0 0         $function->{'c'} and return $function->{'c'};
1734 0           die 'have not implemented recursing if the function is compare type agnostic';
1735             }
1736            
1737 0           set_err('could not evaluate expression: ' . restring(@args));
1738             }
1739             #
1740             # comparetype
1741             #------------------------------------------------------------------------------
1742              
1743              
1744             #------------------------------------------------------------------------------
1745             # sbool
1746             #
1747             sub sbool {
1748 0     0     my $rv = $_[0]->{'rv'};
1749 0 0 0       ($rv and ($rv==RV_BOOL)) or return;
1750 0 0         $_[1] = $_[1] ? 1 : 0;
1751             }
1752             #
1753             # sbool
1754             #------------------------------------------------------------------------------
1755              
1756              
1757             #------------------------------------------------------------------------------
1758             # set_err
1759             #
1760             sub set_err {
1761 0     0     $SQL::YASP::err = 1;
1762 0           $SQL::YASP::errstr = $_[0];
1763 0           return undef;
1764             }
1765             #
1766             # set_err
1767             #------------------------------------------------------------------------------
1768              
1769              
1770             #------------------------------------------------------------------------------
1771             # numeric checking and conversion
1772             #
1773             sub is_numeric {
1774 0 0 0 0     defined($_[0]) and
      0        
1775             (! ref $_[0]) and
1776             $_[0] =~ m|^[\+\-]?\d+\.?$|s
1777             ||
1778             $_[0] =~ m|^[\+\-]?\d*\.\d+$|s;
1779             }
1780              
1781             sub as_number {
1782 0 0   0     is_numeric($_[0]) or $_[0]=0;
1783             }
1784             #
1785             # numeric checking and conversion
1786             #------------------------------------------------------------------------------
1787              
1788              
1789              
1790              
1791             # NUM_BETWEEN
1792             $dbin[OP_BETWEEN]{'between'} = {s=>\&num_between, args=>ARG_RAW, c=>CMP_NUMBER};
1793             sub num_between {
1794 0     0     my ($opts, $expr, $args) = @_;
1795 0           my ($min, $max) = arr_split(['and'], $args, max=>2);
1796            
1797             # $and_str must be "and"
1798 0 0 0       unless (defined($min) && defined($max))
  0            
1799             {croak 'syntax for BETWEEN: $expr BETWEEN $min AND $max'}
1800            
1801 0 0         evalexpr($expr, $opts, $expr) or return;
1802 0 0         evalexpr($min, $opts, $min) or return;
1803 0 0         evalexpr($max, $opts, $max) or return;
1804 0           ($min, $max) = sort($min, $max);
1805            
1806 0   0       return ($expr >= $min) && ($expr <= $max);
1807             }
1808              
1809              
1810             # LOGICAL AND
1811             $dbin[OP_LOGICAL]{'and'} = {args=>ARG_RAW, s=>\&land, c=>CMP_NUMBER};
1812             sub land {
1813 0     0     my ($opts, $left, $right) = @_;
1814            
1815 0 0         evalexpr($left, $opts, $left) or return;
1816            
1817 0 0 0       if (defined($left) or (! $opts->{'parser'}->{'lukas'}))
  0 0          
1818             {$left or return $left}
1819            
1820 0 0         evalexpr($right, $opts, $right) or return;
1821 0 0 0       $right and (! defined $left) and return undef;
1822 0           return $right;
1823             }
1824              
1825             # LOGICAL OR
1826             $dbin[OP_LOGICAL]{'or'} = {args=>ARG_RAW, c=>CMP_NUMBER, s=>sub{
1827             my ($opts, $left, $right) = @_;
1828            
1829             evalexpr($left, $opts, $left) or return;
1830             $left and return $left;
1831            
1832             evalexpr($right, $opts, $right) or return;
1833             ($right or (! $opts->{'parser'}->{'lukas'})) and return $right;
1834              
1835             defined($left) and defined($right) and return $right;
1836             return undef;
1837             }};
1838              
1839             # LOGICAL NAND
1840             # equivalent to "not and"
1841             $dbin[OP_LOGICAL]{'nand'} = {args=>ARG_RAW, c=>CMP_NUMBER, s=>sub{return lnot($_[0], land(@_))}};
1842              
1843             # LOGICAL NOR
1844             # returns true if both arguments are false
1845             $dbin[OP_LOGICAL]{'nor'} = {args=>ARG_RAW, c=>CMP_NUMBER, s=>sub{
1846             my ($opts, $left, $right) = @_;
1847            
1848             evalexpr($left, $opts, $left) or return;
1849             $left and return 0;
1850            
1851             evalexpr($right, $opts, $right) or return;
1852             $right and return 0;
1853            
1854             $opts->{'parser'}->{'lukas'} or return 1;
1855             defined($left) and defined($right) and return 1;
1856             return undef;
1857             }};
1858              
1859             # LOGICAL XOR
1860             # returns true if truth of arguments are different
1861             $dbin[OP_LOGICAL]{'xor'} = {s=>sub{$_[1] xor $_[2]}, rv=>RV_BOOL, c=>CMP_NUMBER};
1862              
1863             # LOGICAL XNOR
1864             # returns true if truth of arguments are the same
1865             $dbin[OP_LOGICAL]{'xnor'} = {s=>sub{( $_[1] && $_[2] ) || ( (! $_[1]) && (! $_[2]) )}, rv=>RV_BOOL, c=>CMP_NUMBER};
1866              
1867             # LIKE
1868             $dbin[OP_MISC]{'like'} = {s=>\&string_like, args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER};
1869             sub string_like {
1870 0     0     my ($opts, $arga, $argb, %bonusopts) = @_;
1871 0           my $esc = '\\';
1872 0           my $i = 1;
1873            
1874             # evaluate $arga
1875 0 0         evalexpr($arga, $opts, $arga) or return;
1876            
1877             # look for escape clause
1878 0           ESCAPELOOP:
1879 0           while ($i < $#{$argb}) {
1880 0 0         if ($argb->[$i] eq 'escape') {
1881 0           my @clause = splice(@{$argb}, $i+1);
  0            
1882 0           pop @{$argb};
  0            
1883 0 0         evalexpr(\@clause, $opts, $esc) or return;
1884 0           last ESCAPELOOP;
1885             }
1886            
1887 0           $i++;
1888             }
1889            
1890             # get value of second argument
1891 0 0         evalexpr($argb, $opts, $argb) or return;
1892            
1893             # substitute * for % and . for _
1894             # use Abigail's fake-look-behind technique
1895 0           $argb = reverse $argb;
1896 0           $esc = quotemeta(reverse $esc);
1897 0           $argb =~ s|\%(?!$esc)|\*\.|sg;
1898 0           $argb =~ s|_|\.|sg;
1899 0           $argb = reverse $argb;
1900            
1901             # if case insensitive
1902 0 0         $bonusopts{'i'} and return $arga =~ m/$argb/i;
1903            
1904             # case sensitive
1905 0           return $arga =~ m/$argb/;
1906             }
1907              
1908              
1909             # ILIKE: case insensitive LIKE
1910             $dbin[OP_MISC]{'ilike'} = {s=>sub{string_like(@_, i=>1)}, args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER};
1911              
1912             # IS
1913             # This one's a little funky. The rules go like this:
1914             # The second batch of arguments are NOT evaluated.
1915             # There are only two possibilities of what may be
1916             # in the second array of arguments: "null", or "not null"
1917             # NULL is synonymous with UNDEF
1918             $dbin[OP_MISC]{'is'} = {s=>\&string_is, args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER};
1919             sub string_is {
1920 0     0     my ($opts, $arg1, $arg2_ref) = @_;
1921 0           my @arg2 = @{$arg2_ref};
  0            
1922            
1923 0 0         evalexpr($arg1, $opts, $arg1) or return;
1924            
1925             # set arg1 to true for defined and has a length
1926 0           $arg1 = defined($arg1);
1927            
1928 0 0 0       if ( (@arg2 == 1) && ($arg2[0] eq 'null') )
  0            
1929             {return ! $arg1}
1930 0 0 0       if ( (@arg2 == 2) && ($arg2[0] eq 'not') && ($arg2[1] eq 'null') )
  0   0        
1931             {return $arg1}
1932            
1933 0           croak 'syntax error: the only arguments for "is" are "null" or "not null"';
1934             }
1935              
1936              
1937             # STRING COMPARISON
1938             $dbin[OP_MISC]{'regexp'} = {s=>sub{$_[1] =~ m/$_[2]/s}, rv=>RV_BOOL, c=>CMP_NUMBER};
1939             $dbin[OP_MISC]{'iregexp'} = {s=>sub{$_[1] =~ m/$_[2]/si}, rv=>RV_BOOL, c=>CMP_NUMBER};
1940             $dbin[OP_MISC]{'<=>'} = $dbin[OP_MISC]{'='} = $dbin[OP_MISC]{'eq'} = {s=>sub{$_[1] eq $_[2]}, rv=>RV_BOOL, c=>CMP_NUMBER};
1941             $dbin[OP_MISC]{'ne'} = {s=>sub{$_[1] ne $_[2]}, rv=>RV_BOOL, c=>CMP_NUMBER};
1942             $dbin[OP_MISC]{'lt'} = {s=>sub{$_[1] lt $_[2]}, rv=>RV_BOOL, c=>CMP_NUMBER};
1943             $dbin[OP_MISC]{'gt'} = {s=>sub{$_[1] gt $_[2]}, rv=>RV_BOOL, c=>CMP_NUMBER};
1944             $dbin[OP_MISC]{'eqi'} = {s=>sub{lc($_[1]) eq lc($_[2])}, rv=>RV_BOOL, c=>CMP_NUMBER};
1945             $dbin[OP_MISC]{'nei'} = {s=>sub{lc($_[1]) ne lc($_[2])}, rv=>RV_BOOL, c=>CMP_NUMBER};
1946             $dbin[OP_MISC]{'lti'} = {s=>sub{lc($_[1]) lt lc($_[2])}, rv=>RV_BOOL, c=>CMP_NUMBER};
1947             $dbin[OP_MISC]{'gti'} = {s=>sub{lc($_[1]) gt lc($_[2])}, rv=>RV_BOOL, c=>CMP_NUMBER};
1948              
1949              
1950             # regular expression
1951             $dbin[OP_MISC]{'=~'} = {s=>\&rxmatch, rv=>RV_BOOL, c=>CMP_NUMBER};
1952             sub rxmatch {
1953 0     0     my ($opts, $str, $rx) = @_;
1954 0           my $not = 'xism';
1955            
1956 0 0         $rx->{'params'} and $not =~ s|[$rx->{'params'}]||g;
1957 0           $rx = "(?$rx->{'params'}-$not:$rx->{'rx'})";
1958 0           $rx =~ s|^(\(\?[xism]{4})-|$1|s;
1959 0           $str =~ /$rx/;
1960             }
1961              
1962              
1963             # IN
1964             $dbin[OP_MISC]{'in'} = {s=>\&string_in, args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER};
1965             sub string_in {
1966 0     0     my ($opts, $arg1, $arg2, %bonusopts) = @_;
1967 0           my $ci = $bonusopts{'i'};
1968            
1969             # get string value for argument 1
1970 0 0         evalexpr($arg1, $opts, $arg1) or return;
1971 0 0         $ci and $arg1 =~ tr/A-Z/a-z/;
1972            
1973             # loop through arg2 values
1974 0           foreach my $choice (comma_split([deref_args($arg2)])) {
1975 0 0         evalexpr($choice, $opts, $choice) or return;
1976 0 0         $ci and $choice =~ tr/A-Z/a-z/;
1977 0 0         ($arg1 eq $choice) and return 1;
1978             }
1979            
1980 0           return 0;
1981             }
1982              
1983             # IIN: case insensitive IN
1984             # not in MYSQL
1985             $dbin[OP_MISC]{'iin'} = {s=>sub{return string_in(@_, i=>1)}, args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER};
1986              
1987             # NUMERIC COMPARISONS
1988             $dbin[OP_MISC]{'>'} = {s=>sub{$_[1] > $_[2]}, args=>ARG_NUMERIC, rv=>RV_BOOL, c=>CMP_NUMBER};
1989             $dbin[OP_MISC]{'<'} = {s=>sub{$_[1] < $_[2]}, args=>ARG_NUMERIC, rv=>RV_BOOL, c=>CMP_NUMBER};
1990             $dbin[OP_MISC]{'>='} = {s=>sub{$_[1] >= $_[2]}, args=>ARG_NUMERIC, rv=>RV_BOOL, c=>CMP_NUMBER};
1991             $dbin[OP_MISC]{'<='} = {s=>sub{$_[1] <= $_[2]}, args=>ARG_NUMERIC, rv=>RV_BOOL, c=>CMP_NUMBER};
1992             $dbin[OP_MISC]{'=='} = {s=>sub{$_[1] == $_[2]}, args=>ARG_NUMERIC, rv=>RV_BOOL, c=>CMP_NUMBER};
1993              
1994             # NUMERIC NOT EQUAL: different than MySql, where <> is the same as !=
1995             $dbin[OP_MISC]{'<>'} = {s=>sub{$_[1] != $_[2]}, args=>ARG_NUMERIC, rv=>RV_BOOL, c=>CMP_NUMBER};
1996              
1997              
1998             # CONCATENATION
1999             $dbin[OP_MISC]{'||'} = {s=>sub{ (defined($_[1]) ? $_[1] : '') . (defined($_[2]) ? $_[2] : '')}, args=>ARG_SENDNULLS, c=>CMP_STRING};
2000             $dbin[OP_MISC]{'|||'} = {args=>ARG_SENDNULLS, c=>CMP_STRING, s=>sub{
2001             my $space = (defined($_[1]) && defined($_[2]) && ($_[1] =~ m|\S$|) && ($_[2] =~ m|^\S|) ) ? ' ' : '';
2002             (defined($_[1]) ? $_[1] : '') . $space . (defined($_[2]) ? $_[2] : '');
2003             }};
2004              
2005             # NUMERIC OPERATIONS
2006             $dbin[OP_ADD]{'-'} = {s=>sub{$_[1] - $_[2]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2007             $dbin[OP_ADD]{'+'} = {s=>sub{$_[1] + $_[2]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2008             $dbin[OP_MULT]{'*'} = {s=>sub{$_[1] * $_[2]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2009             $dbin[OP_MULT]{'%'} = {s=>sub{$_[1] % $_[2]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2010             $dbin[OP_EXP]{'^'} = {s=>sub{$_[1] ** $_[2]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2011             $dbin[OP_MULT]{'%'} = {args=>ARG_NUMERIC, c=>CMP_NUMBER, s=>sub{
2012             $_[2] or return set_err('divide by zero');
2013             $_[1] % $_[2];
2014             }};
2015             $dbin[OP_MULT]{'/'} = {args=>ARG_NUMERIC, c=>CMP_NUMBER, s=>sub{
2016             $_[2] or return set_err('divide by zero');
2017             $_[1] / $_[2];
2018             }};
2019              
2020              
2021             # TOLOWER, LCASE, LOWER
2022             $dfuncs{'tolower'} =
2023             $dfuncs{'lcase'} =
2024             $dfuncs{'lower'} =
2025             {s=>sub{lc($_[1])}, c=>CMP_STRING};
2026              
2027             # TOUPPER, UCASE, UPPER
2028             $dfuncs{'toupper'} =
2029             $dfuncs{'ucase'} =
2030             $dfuncs{'upper'} =
2031             { s=>sub{uc($_[1])}, c=>CMP_STRING};
2032              
2033             # TOTITLE, TCASE, TITLE
2034             $dfuncs{'totitle'} =
2035             $dfuncs{'tcase'} =
2036             $dfuncs{'title'} = {
2037             s=>sub{
2038             my $rv = lc($_[1]);
2039             $rv =~ s|\b(.)|\U$1|sg;
2040             $rv;
2041             },
2042             c=>CMP_STRING
2043             };
2044              
2045             # NOT: negate results
2046             $dfuncs{'not'} = {s=>\&lnot, args=>ARG_SENDNULLS, c=>CMP_NUMBER};
2047             sub lnot {
2048 0 0 0 0     $_[0]->{'parser'}->{'lukas'} and (! defined $_[1]) and return undef;
2049 0 0         return $_[1] ? 0 : 1;
2050             }
2051              
2052             # ERR: sets an error
2053             $dfuncs{'err'} = {s=>sub{return set_err($_[1])}};
2054              
2055             # ISNULL: returns true if the given value is NOT defined
2056             $dfuncs{'isnull'} = {s=>sub{! defined $_[1]}, args=>ARG_SENDNULLS, c=>CMP_NUMBER, rv=>RV_BOOL};
2057              
2058             # DEFINED: returns true if *all* of the given values are defined
2059             # empty strings count as defined
2060             $dfuncs{'defined'} = {args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER, s=>sub{
2061             my ($opts, @args) = @_;
2062             my ($val);
2063            
2064             foreach my $arg (comma_split(\@args)) {
2065             evalexpr($arg, $opts, $val) or return;
2066             defined($val) or return 0;
2067             }
2068            
2069             return 1;
2070             }};
2071              
2072             # HASCONTENT: returns true if the given value is defined
2073             # and has at least one non-space character
2074             $dfuncs{'hascontent'} = {s=>sub{$_[1] =~ m|\S|}, rv=>RV_BOOL, c=>CMP_NUMBER};
2075              
2076             # HASNULL: returns true if *any* the given values are null
2077             $dfuncs{'hasnull'} = {args=>ARG_RAW, rv=>RV_BOOL, c=>CMP_NUMBER, s=>sub{
2078             my ($opts, @args) = @_;
2079             my ($val);
2080            
2081             foreach my $arg (comma_split(\@args)) {
2082             evalexpr($arg, $opts, $val) or return;
2083             defined($val) or return 1;
2084             }
2085            
2086             return 0;
2087             }};
2088              
2089             # NULL, TRUE, FALSE
2090             $dfuncs{'undef'} = $dfuncs{'null'} = {s=>sub{undef}, args=>ARG_NONE, c=>CMP_NUMBER};
2091             $dfuncs{'true'} = {s=>sub{1}, args=>ARG_NONE, c=>CMP_NUMBER};
2092             $dfuncs{'false'} = {s=>sub{0}, args=>ARG_NONE, c=>CMP_NUMBER};
2093              
2094             # IF
2095             $dfuncs{'if'} = {s=>\&func_if, args=>ARG_RAW};
2096             sub func_if {
2097 0     0     my ($opts, @args) = @_;
2098 0           my ($expr, $true, $false) = comma_split(\@args);
2099 0           my ($val);
2100            
2101 0 0         evalexpr($expr, $opts, $val) or return;
2102            
2103 0 0         if ($val) {
2104 0 0         evalexpr($true, $opts, $val) or return;
2105 0           return $val
2106             }
2107            
2108 0 0 0       unless ($false and @{$false})
  0            
  0            
2109             {return undef}
2110            
2111 0 0         evalexpr($false, $opts, $val) or return;
2112 0           return $val;
2113             }
2114              
2115              
2116             # CAT, CONCAT
2117             # returns all arguments concatenated together
2118             # Following the MySql documentation, this function returns NULL
2119             # if any argument is null. That seems a little harsh to me. If
2120             # you feel like I misread the documentation on that feel free
2121             # to drop me an email on the matter: miko@idocs.com
2122             $dfuncs{'cat'} = $dfuncs{'concat'} = {c=>CMP_STRING, s=>sub{shift;grep {defined($_) or return undef} @_;join('', @_)}};
2123              
2124              
2125             # CONCAT_WS
2126             # returns all arguments concatenated together with a separator
2127             # Following the MySql documentation, this function returns NULL
2128             # if the first argument is null, but nulls after that are ignored
2129             # (not counted as part of the returned string).
2130             $dfuncs{'cat_ws'} = $dfuncs{'concat_ws'} = {s=>\&concat_ws, c=>CMP_STRING};
2131             sub concat_ws {
2132 0     0     shift;
2133 0           my ($sep, @args) = @_;
2134 0 0         defined($sep) or return(undef);
2135 0           return join($sep, grep {defined $_} @args);
  0            
2136             }
2137              
2138             # COALESCE
2139             $dfuncs{'coalesce'} = {s=>\&coalesce};
2140             sub coalesce {
2141 0     0     shift;
2142 0 0         foreach (@_)
  0            
2143             {defined($_) and return $_}
2144 0           return undef;
2145             }
2146              
2147             # LOAD_FILE
2148             $dfuncs{'load_file'} = {s=>\&load_file, c=>CMP_STRING};
2149             sub load_file {
2150 0     0     require FileHandle;
2151 0 0         my $fh = FileHandle->new($_[1]) or return undef;
2152 0           return join('', <$fh>);
2153             }
2154              
2155             #------------------------------------------------------------------------------
2156             # mathematical functions
2157             #
2158              
2159             # ORD, OCT, HEX, ABS, SIGN
2160             $dfuncs{'ord'} = {s=>sub{ord $_[1]}, c=>CMP_NUMBER};
2161             $dfuncs{'oct'} = {s=>sub{oct $_[1]}, c=>CMP_NUMBER};
2162             $dfuncs{'hex'} = {s=>sub{hex $_[1]}, c=>CMP_NUMBER};
2163             $dfuncs{'abs'} = {s=>sub{abs $_[1]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2164             $dfuncs{'sign'} = {s=>sub{$_[1] or return 0;($_[1] > 0) ? 1 : -1;}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2165              
2166             # MOD
2167             $dfuncs{'mod'} = {s=>sub{$_[1] % $_[2]}, c=>CMP_NUMBER};
2168              
2169             # POW, POWER
2170             $dfuncs{'pow'} = $dfuncs{'power'} = {s=>sub{$_[1] ** $_[2]}, c=>CMP_NUMBER};
2171              
2172             # FLOOR
2173             $dfuncs{'floor'} = {s=>\&floor, c=>CMP_NUMBER};
2174             sub floor {
2175 0 0   0     ($_[1] >= 0) and return int($_[1]);
2176 0 0         ($_[1] =~ m|\.0*[1-9]|) ? int($_[1]-1) : $_[1];
2177             }
2178              
2179             # CEILING
2180             $dfuncs{'ceil'} = $dfuncs{'ceiling'} = {s=>\&ceil, c=>CMP_NUMBER};
2181             sub ceil {
2182 0 0   0     ($_[1] <= 0) and return int($_[1]);
2183 0 0         ($_[1] =~ m|\.0*[1-9]|) ? int($_[1]+1) : $_[1];
2184             }
2185              
2186             # INT
2187             $dfuncs{'int'} = $dfuncs{'ceiling'} = {s=>sub{int($_[1])}, c=>CMP_NUMBER};
2188              
2189              
2190             # SQUARE, SQUARED
2191             $dfuncs{'square'} = $dfuncs{'squared'} = {s=>sub{$_[1] ** 2}, c=>CMP_NUMBER};
2192              
2193              
2194             # unary minus
2195             $dfuncs{'-'} = {s=>sub{$_[1] * -1}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2196              
2197             # unary plus
2198             # this rather useless looking function allows us to
2199             # have expressions like this: 1/+2
2200             $dfuncs{'+'} = {s=>sub{$_[1]}, args=>ARG_NUMERIC, c=>CMP_NUMBER};
2201              
2202             #
2203             # mathematical functions
2204             #------------------------------------------------------------------------------
2205              
2206              
2207             # CHAR
2208             $dfuncs{'char'} = {s=>\&char, c=>CMP_STRING};
2209             sub char {
2210 0     0     shift;
2211 0           my(@rv);
2212 0           foreach my $el (@_)
  0            
2213             {push @rv, chr int $el}
2214 0           return join('', @rv);
2215             }
2216              
2217              
2218             # STRING MANIPULATION AND INFORMATION
2219             $dfuncs{'length'} = {s=>sub{length $_[1]}, c=>CMP_NUMBER};
2220             $dfuncs{'ltrim'} = {s=>sub{$_[1] =~ s|\s+$||s;$_[1];}, c=>CMP_STRING};
2221             $dfuncs{'rtrim'} = {s=>sub{$_[1]=~s|^\s+||s;$_[1];}, c=>CMP_STRING};
2222             $dfuncs{'left'} = {s=>sub{substr($_[1],0,$_[2])}, c=>CMP_STRING};
2223             $dfuncs{'right'} = {s=>sub{reverse(substr(reverse($_[1]), 0, $_[2]))}, c=>CMP_STRING};
2224             $dfuncs{'reverse'} = {s=>sub{reverse($_[1])}, c=>CMP_STRING};
2225             $dfuncs{'space'} = {s=>sub{' ' x $_[1]}, c=>CMP_STRING};
2226             $dfuncs{'repeat'} = {s=>sub{defined($_[1]) && defined($_[2]) or return(undef);$_[1] x $_[2]}, c=>CMP_STRING};
2227             $dfuncs{'insert'} = {s=>sub{substr($_[1], $_[2]-1, $_[3]) = $_[4];$_[1]}, c=>CMP_STRING};
2228              
2229              
2230             # REPLACE
2231             $dfuncs{'replace'} = {s=>\&replace, c=>CMP_STRING};
2232             sub replace {
2233 0     0     shift;
2234 0           my ($str, $from, $to) = @_;
2235 0           $from = quotemeta($from);
2236 0           $str =~ s/$from/$to/i;
2237 0           $str;
2238             }
2239              
2240             # QUOTE
2241             # needs to be fixed, doesn't quote enough stuff
2242             # $dfuncs{'quote'} = {s=>sub{my($v)=@_;$v =~ s|'|\\'|gs;$v}, c=>CMP_STRING};
2243              
2244              
2245             # SOUNDEX
2246             # this function returns shorter values than the
2247             # MySql documentation, so this function may not work as expected
2248             $dfuncs{'soundex'} = {s=>sub{require Text::Soundex;Text::Soundex::soundex($_[1])}, c=>CMP_STRING};
2249              
2250             # STRCMP
2251             $dfuncs{'strcmp'} = $dfuncs{'cmp'} = {s=>sub{$_[1] cmp $_[2]}, c=>CMP_NUMBER};
2252              
2253             # LOCATE and friends
2254             $dfuncs{'locate'} = $dfuncs{'position'} = {s=>\&locate, c=>CMP_NUMBER};
2255             $dfuncs{'instr'} = {s=>sub{locate(@_[2,1,3])}};
2256             sub locate {
2257 0   0 0     $_[3] ||= 1;
2258 0           index(lc($_[2]), lc($_[1]), $_[3]-1)+1;
2259             }
2260              
2261             # CRUNCH
2262             # remove leading and trailing spaces,
2263             # reduce internal contigous spaces to single spaces
2264             $dfuncs{'crunch'} = {s=>\&crunch, c=>CMP_STRING};
2265             sub crunch {
2266 0     0     my $rv = $_[1];
2267 0           $rv =~ s|^\s+||s;
2268 0           $rv =~ s|\s+$||s;
2269 0           $rv =~ s|\s+| |sg;
2270 0           $rv;
2271             }
2272              
2273             # TRIM
2274             # syntax: TRIM([[BOTH | LEADING | TRAILING] [remstr] FROM] str)
2275             $dfuncs{'trim'} = {s=>\&trim, args=>ARG_RAW, c=>CMP_STRING};
2276             sub trim {
2277 0     0     shift;
2278 0           my ($opts, @args) = @_;
2279 0           my ($leading, $trailing, $next, $left, $str, $regex);
2280            
2281             # get before and after FROM
2282 0           ($left, $str) = arr_split(['from'], @args);
2283            
2284             # early exit: no FROM, so just trim and return
2285 0 0         if (! $str) {
2286 0 0         evalexpr($left, $opts, $str) or return;
2287 0           $str =~ s|^\s+||s;
2288 0           $str =~ s|\s+$||s;
2289 0           return $str;
2290             }
2291            
2292 0 0         evalexpr($str, $opts, $str) or return;
2293 0           @args = @$left;
2294            
2295             # determine leading and trailing trim actions
2296 0   0       while (
2297             @args &&
2298             ($args[0] =~ m/^(both|leading|trailing)$/)
2299             ) {
2300 0   0       $leading ||= $args[0] =~ m/^(both|leading)$/;
2301 0   0       $trailing ||= $args[0] =~ m/^(both|trailing)$/;
2302 0           shift @args;
2303             }
2304            
2305             # "If none of the specifiers BOTH, LEADING or TRAILING are given, BOTH is assumed."
2306             # -- MySql docs
2307 0 0 0       unless ($leading || $trailing)
  0            
2308             {$leading = $trailing = 1}
2309            
2310             # left defaults to \s
2311 0 0         if (@args) {
  0            
2312 0 0         evalexpr(\@args, $opts, $regex) or return;
2313 0           $regex = quotemeta($regex);
2314             }
2315             else
2316             {$regex = '\s'}
2317            
2318 0 0         $leading and $str =~ s/^($regex)+//s;
2319 0 0         $trailing and $str =~ s/($regex)+$//s;
2320 0           return $str;
2321             }
2322              
2323              
2324             # LPAD
2325             $dfuncs{'lpad'} = {s=>\&lpad, c=>CMP_STRING};
2326             sub lpad {
2327 0     0     shift;
2328 0           my @str = split('', shift);
2329 0           my $len = shift;
2330 0           my @padstr = split('', shift);
2331 0 0         @padstr or @padstr = (' ');
2332            
2333 0           while (@str < $len)
  0            
2334             {unshift @str, @padstr}
2335 0           while (@str > $len)
  0            
2336             {shift @str}
2337            
2338 0           return join('', @str);
2339             }
2340              
2341              
2342             # RPAD
2343             $dfuncs{'rpad'} = {s=>\&rpad, c=>CMP_STRING};
2344             sub rpad {
2345 0     0     shift;
2346 0           my @str = split('', shift);
2347 0           my $len = shift;
2348 0           my @padstr = split('', shift);
2349 0 0         @padstr or @padstr = (' ');
2350            
2351 0           while (@str < $len)
  0            
2352             {push @str, @padstr}
2353 0           while (@str > $len)
  0            
2354             {pop @str}
2355            
2356 0           return join('', @str);
2357             }
2358              
2359              
2360             # SUBSTRING
2361             $dfuncs{'substring'} =
2362             $dfuncs{'mid'} =
2363             $dfuncs{'substr'} =
2364             {s=>\&substring, args=>ARG_RAW, c=>CMP_STRING};
2365             sub substring {
2366 0     0     my ($opts, @args) = @_;
2367 0           my ($str, $pos, $len) = arr_split([',', 'from', 'for'], @args);
2368 0 0         evalexpr($str, $opts, $str) or return;
2369 0 0         evalexpr($pos, $opts, $pos) or return;
2370            
2371 0 0         if ($len)
  0 0          
2372 0           {evalexpr($len, $opts, $len) or return}
2373             else
2374             {$len = length($str)}
2375            
2376 0           return substr($str, $pos-1, $len);
2377             }
2378              
2379              
2380             # SUBSTRING_INDEX
2381             $dfuncs{'substring_index'} = {s=>\&substring_index, c=>CMP_STRING};
2382             sub substring_index {
2383 0     0     shift;
2384 0           my ($str, $del, $count) = @_;
2385 0           my (@arr, $reverse, $del_esc);
2386            
2387 0           $del_esc = quotemeta($del);
2388            
2389 0 0         if ($count < 0) {
2390 0           $reverse = 1;
2391 0           $count *= -1;
2392             }
2393            
2394 0           @arr = split($del_esc, $str);
2395 0 0         $reverse and @arr = reverse @arr;
2396            
2397 0 0         if (@arr > $count)
  0            
2398             {@arr = @arr[0..($count-1)]}
2399            
2400 0 0         $reverse and @arr = reverse @arr;
2401 0           return join($del, @arr);
2402             }
2403              
2404              
2405             # ELT
2406             $dfuncs{'elt'} = {s=>\&elt, c=>CMP_AGNOSTIC};
2407             sub elt {
2408 0     0     shift;
2409 0           my $val=shift;
2410 0           return $_[$val-1];
2411             }
2412              
2413             # FIELD
2414             $dfuncs{'field'} = {s=>\&field, c=>CMP_AGNOSTIC};
2415             sub field {
2416 0     0     shift;
2417 0           my $val=lc(shift);
2418 0           my $i = 0;
2419            
2420 0           while ($i <= $#_) {
2421 0 0         if (lc($_[$i]) eq $val)
  0            
2422             {return $i+1}
2423 0           $i++;
2424             }
2425            
2426 0           return undef;
2427             }
2428              
2429             #
2430             # SQL::YASP::Expr
2431             ###############################################################################
2432              
2433              
2434             # return true;
2435             1;
2436              
2437             __END__