File Coverage

blib/lib/Neo4j/Cypher/Abstract/Peeler.pm
Criterion Covered Total %
statement 214 264 81.0
branch 125 178 70.2
condition 67 154 43.5
subroutine 29 33 87.8
pod 6 21 28.5
total 441 650 67.8


line stmt bran cond sub pod time code
1             package Neo4j::Cypher::Abstract::Peeler;
2 6     6   199246 use Carp;
  6         40  
  6         481  
3 6     6   41 use List::Util 1.33 qw(any);
  6         139  
  6         483  
4 6     6   36 use Scalar::Util qw(looks_like_number blessed);
  6         10  
  6         314  
5 6     6   33 use strict;
  6         12  
  6         201  
6 6     6   30 use warnings;
  6         16  
  6         1164  
7              
8             # quoting logic:
9             # if config:bind = true
10             # if anon_placeholder (like ?) is in config, then return literals without
11             # quoting in array $obj->bind_values, and the placeholder in the statement
12             # if anon_placeholder is undef, then return literals quoted directly in the
13             # statement; return named parameters in $obj->parameters
14             #
15             # if config:bind false
16             # leave tokens and identifiers as-is, no bind_values or parameters
17              
18             our $VERSION = '0.1002';
19             my $SQL_ABSTRACT = 1;
20              
21             sub puke(@);
22             sub belch(@);
23              
24             my %config = (
25             bind => 1,
26             anon_placeholder => undef, # '?',
27             hash_op => '-and',
28             array_op => '-or',
29             list_braces => '[]',
30             ineq_op => '<>',
31             implicit_eq_op => '=',
32             quote_lit => "'",
33             esc_quote_lit => "\\",
34             parameter_sigil => qw/^([:$?])|({[^}]+}$)/,
35             quote_fld => undef,
36             safe_identifier => qw/[a-zA-Z_.]+/
37             );
38              
39             # for each operator type (key in %type_table), there
40             # should be a handler with the same name
41              
42             my %type_table = (
43             infix_binary => [qw{ - / % =~ = <> < > <= >=
44             -contains -starts_with -ends_with}],
45             infix_listarg => [qw{ -in } ],
46             infix_distributable => [qw{ + * -and -or }],
47             prefix => [qw{ -not }],
48             postfix => [qw{ -is_null -is_not_null }],
49             function => [qw{
50             ()
51             -abs -ceil -floor -rand -round -sign -degrees
52             -e -exp -log -log10 -sqrt -acos -asin -atan -atan2
53             -cos -cot -haversin -pi -radians -sin -tan
54             -exists -toInt
55             -left -lower -ltrim -replace -reverse -right
56             -rtrim -split -substring -toString -trim -upper
57             -length -size -type -id -coalesce -head -last
58             -labels -nodes -relationships -keys -tail -range
59             -collect -count -max -min -percentileCont
60             -percentileDisc -stDev -stDevP -sum
61             -shortestPath -allShortestPaths
62             }],
63             predicate => [qw{ -all -any -none -single -filter}],
64             extract => [qw{ -extract }],
65             reduce => [qw{ -reduce }],
66             list => [qw( -list )], # returns args in list format
67             bind => [qw( -bind )], # handles parameters and literal quoting
68             thing => [qw( -thing )], # the literal thing itself
69             );
70              
71             my %dispatch;
72             foreach my $type (keys %type_table) {
73 6     6   39 no strict 'refs';
  6         10  
  6         20794  
74             my @ops = @{$type_table{$type}};
75             @dispatch{@ops} = ( *${type}{CODE} ) x @ops;
76             }
77              
78             sub new {
79 36     36 0 2924 my $class = shift;
80 36         59 my %args = @_;
81 36 50 33     97 if ($args{dispatch} and !(ref $args{dispatch} eq 'HASH')) {
82 0         0 puke "dispatch must be hashref mapping operators to coderefs (or absent)"
83             }
84 36 50 33     80 if ($args{config} and !(ref $args{config} eq 'HASH')) {
85 0         0 puke "config must be hashref defining peeler options"
86             }
87             my $self = {
88             dispatch => $args{dispatch} || \%dispatch,
89 36   50     197 config => $args{config} || \%config
      50        
90             };
91             # update config elts according to constructor args
92 36 50       111 if (length scalar keys %args) {
93 36         151 for (keys %config) {
94 432 50       645 defined $args{$_} and $self->{config}{$_} = $args{$_};
95             }
96             }
97 36         127 bless $self, $class;
98             }
99              
100             sub belch (@) {
101 0     0 0 0 my($func) = (caller(1))[3];
102 0         0 Carp::carp "[$func] Warning: ", @_;
103             }
104              
105             sub puke (@) {
106 0     0 0 0 my($func) = (caller(1))[3];
107 0         0 Carp::croak "[$func] Fatal: ", @_;
108             }
109              
110 32 100   32 1 102 sub bind_values { $_[0]->{bind_values} ? @{$_[0]->{bind_values}} : return ; }
  24         58  
111 32 50   32 1 90 sub parameters { $_[0]->{parameters} ? @{$_[0]->{parameters}} : return ; }
  0         0  
112              
113             sub _dispatch {
114 252     252   635 $_[0]->{dispatch}{$_[1]}->(@_);
115             }
116              
117             sub _quote_lit {
118 171     171   249 my $arg = "$_[1]";
119 171         248 my $q = $_[0]->{config}{quote_lit};
120 171 100 66     1263 if (looks_like_number $arg or
      66        
      66        
121             $arg =~ /^\s*$q(.*)$q\s*$/ or
122             $arg =~ $_[0]->{config}{parameter_sigil} or
123             blessed($_[1])
124             ) {
125             # numeric, already quoted, a parameter, or an object
126 72         178 return "$arg";
127             }
128             else {
129 99         177 my $e = $_[0]->{config}{esc_quote_lit};
130 99         190 $arg =~ s/$q/$e$q/g;
131 99         305 return "$q$arg$q";
132             }
133             }
134              
135             sub _quote_fld { # noop
136 0     0   0 return $_[1];
137             }
138              
139             sub express {
140 32     32 1 44 my $self = shift;
141 32         40 my $x = $_[0];
142 32 50       54 if ($SQL_ABSTRACT) {
143 32         72 $x = $self->canonize($x);
144             }
145 32         84 return $self->peel($x);
146             }
147              
148             sub config {
149 1     1 1 2 my $self = shift;
150 1         3 my ($key, $val) = @_;
151 1 50       4 if (!defined $key) {
    50          
152 0         0 return %{$self->{config}};
  0         0  
153             }
154             elsif (!defined $val) {
155 0         0 return $self->{config}{$key};
156             }
157             else {
158 1         5 return $self->{config}{$key} = $val;
159             }
160             }
161              
162              
163             # canonize - rewrite mixed hash/array expressions in canonical lispy
164             # array format - interpret like SQL::A
165             sub canonize {
166 71     71 1 35581 my $self = shift;
167 71         141 my ($expr) = @_;
168 71         115 my $ret = [];
169 71         111 my ($do,$is_op);
170             $is_op = sub {
171 546 100 66 546   1556 if (!defined $_[0] || ref $_[0]) {
172 8         26 return 0;
173             }
174 538 100       782 if (!$_[1]) {
175 215 100       508 if (defined $self->{dispatch}{$_[0]}) {
176 63         137 1;
177             }
178             else {
179 152 50 66     755 puke "Unknown operator '$_[0]'" if (
      33        
180             $_[0] !~ /$$self{config}{safe_identifier}/ and
181             $_[0]=~/^-|[[:punct:]]/ and !looks_like_number($_[0])
182             );
183 152         574 0;
184             }
185             }
186             else {
187 323         383 grep /^\Q$_[0]\E$/, @{$type_table{$_[1]}};
  323         5396  
188             }
189 71         250 };
190 71         117 my $level=0;
191             $do = sub {
192 346     346   1051 my ($expr, $lhs, $arg_of) = @_;
193 346         809 for (ref $expr) {
194 346 100 100     1238 ($_ eq '' or blessed($expr)) && do {
195 113 50       184 if (defined $expr) {
196             # literal (value?)
197 113 100       765 return $self->{config}{bind} ? [ -bind => $expr ] : $expr;
198             }
199             else {
200 0         0 puke "undef not interpretable";
201             }
202             };
203 233 100       812 /REF|SCALAR/ && do { # literals
204 22 100       54 if ($_ eq 'SCALAR') {
    50          
205 17         88 return [-thing => $$expr] ; # never bind
206             }
207             elsif (ref $$expr eq 'ARRAY') {
208             # very SQL:A thing, but we'll do it
209 5         12 my @a = @$$expr;
210 5         9 my $thing = shift @a;
211 5         16 @a = map { $self->_quote_lit($_) } @a;
  5         11  
212 5 50       16 if ($self->{config}{bind}) {
213 0         0 push @{$self->{bind_values}}, @a;
  0         0  
214             }
215             else {
216 5 100       10 if ($self->{config}{anon_placeholder}) {
217 4         37 ($thing =~ s/\Q$$self{config}{anon_placeholder}\E/$_/) for @a;
218             }
219             }
220 5 100       61 return $lhs ? [-thing => $lhs, [-thing => $thing]] : [-thing => $thing];
221             }
222             };
223 211 100       395 /ARRAY/ && do {
224 32 100       69 if ($is_op->($$expr[0],'infix_distributable')) {
225             # handle implicit equality pairs in an array
226 12         32 my $op = shift @$expr;
227 12         25 my (@args,@flat);
228             # flatten
229 12 100       41 if (@$expr == 1) {
230 3         39 for (ref($$expr[0])) {
231 3 50       30 /ARRAY/ && do {
232 3         6 @flat = @{$$expr[0]};
  3         10  
233 3         6 last;
234             };
235 0 0       0 /HASH/ && do {
236 0         0 @flat = %{$$expr[0]};
  0         0  
237 0         0 last;
238             };
239 0         0 puke 'Huh?';
240             };
241             }
242             else {
243 9         34 @flat = @$expr; # already flat
244             }
245 12         39 while (@flat) {
246 25         42 my $elt = shift @flat;
247             # $DB::single=1 if !defined($elt);
248 25 100       55 if (!ref $elt) { # scalar means lhs of a pair or another op
249 14         127 push @args, $do->({$elt => shift @flat},$lhs,$op);
250             }
251             else {
252 11 100 66     81 next if (ref $elt eq 'ARRAY') && ! scalar @$elt or
      100        
      66        
253             (ref $elt eq 'HASH') && ! scalar %$elt;
254 10         105 push @args, $do->($elt,$lhs,$op);
255             }
256             }
257 12         61 return [$op => @args];
258             }
259 20 100 66     49 if ($is_op->($$expr[0]) and !$is_op->($$expr[0],'infix_distributable')) {
    100          
260             # some other op
261             return [ $$expr[0] => map {
262 4         14 $do->($_,undef,$$expr[0])
  7         54  
263             } @$expr[1..$#$expr] ];
264             }
265             elsif (ref $$expr[0] eq 'HASH') { #?
266             return [ $self->{config}{array_op} =>
267 3         11 map { $do->($_,$lhs,$self->{config}{array_op}) } @$expr ];
  6         40  
268             }
269             else { # is a plain list
270 13 100       31 if ($lhs) {
271             # implicit equality over array default op
272             return [ $self->{config}{array_op} => map {
273 4         12 defined() ?
274             [ $self->{config}{implicit_eq_op} => $lhs,
275 13 100       70 $do->($_,undef,$self->{config}{implicit_eq_op}) ] :
276             [ -is_null => $lhs ]
277             } @$expr ];
278             }
279             else {
280 9 100 66     57 if ($arg_of and any { $is_op->($arg_of, $_) }
  15         32  
281             qw/function infix_listarg predicate reduce/
282             ) {
283             # function argument - return list itself
284 6         16 return [ -list => map { $do->($_) } @$expr ];
  16         137  
285             }
286             else {
287             # distribute $array_op over implicit equality
288 3         54 return $do->([ $self->{config}{array_op} => @$expr ]);
289             }
290             }
291             }
292             };
293 179 50       449 /HASH/ && do {
294 179         589 my @k = keys %$expr;
295             #######
296 179 100       731 if (@k == 1) {
297 156         246 my $k = $k[0];
298             # single hashpair
299 156 100 100     295 if ($is_op->($k)) {
    100          
300 59 100       97 $is_op->($k,'infix_binary') && do {
301 33 50       87 puke "Expected LHS for $k" unless $lhs;
302 33 100       98 if (ref $$expr{$k} eq 'ARRAY') {
    100          
303             # apply binary operator and join with array default op
304             return [ $self->{config}{array_op} => map {
305             defined() ?
306             [ $k => $lhs, $do->($_)] :
307             { $self->{config}{ineq_op} => [-is_not_null => $lhs],
308             $self->{config}{implicit_eq_op} => [-is_null => $lhs]
309 2 100       23 }->{$k}
310 1         3 } @{$$expr{$k}} ]
  1         3  
311             }
312             elsif (defined $$expr{$k}) {
313 29         462 return [ $k => $lhs, $do->($$expr{$k},undef,$k) ]; #?
314             }
315             else { # IS (NOT) NULL
316 3 50       21 $k eq $self->{config}{ineq_op} && do {
317 3         16 return [ -is_not_null => $lhs ];
318             };
319 0 0       0 $k eq $self->{config}{implicit_eq_op} && do {
320 0         0 return [ -is_null => $lhs ];
321             };
322 0         0 puke "Can't handle undef as argument to $k";
323             }
324             };
325 26 100       67 $is_op->($k,'function') && do {
326 6         122 return [ $k => $do->($$expr{$k},undef,$k) ];
327             };
328 20 100       47 $is_op->($k,'infix_listarg') && do {
329 4         65 return [ $k => $lhs, $do->($$expr{$k},undef,$k) ];
330             };
331 16 100       33 $is_op->($k,'prefix') && do {
332 8         126 return [ $k => $do->($$expr{$k}) ];
333             };
334 8 100       27 $is_op->($k,'infix_distributable') && do {
335 4 50 33     32 if (!ref $$expr{$k} && $lhs) {
    100          
    50          
336 0         0 return [ $k => $lhs, $do->($$expr{$k}) ];
337             }
338             elsif ( ref $$expr{$k} eq 'HASH' ) {
339 1         2 my @ar = %{$$expr{$k}};
  1         4  
340 1         3 return $do->([$k=>@ar]); #?
341             }
342             elsif ( ref $$expr{$k} eq 'ARRAY') {
343 3         45 return $do->([$k => $$expr{$k}]);
344             }
345             else {
346 0         0 puke "arg type '".ref($$expr{$k})."' not expected for op '$k'";
347             }
348             };
349 4 100       10 $is_op->($k,'predicate') && do {
350             puke "predicate function '$k' requires an length 3 arrayref argument"
351 2 50 33     7 unless ref $$expr{$k} eq 'ARRAY' and @{$$expr{$k}} == 3;
  2         9  
352             return [ $k => [-thing => $$expr{$k}->[0]],
353             $do->($$expr{$k}->[1], undef, $k),
354 2         31 $do->($$expr{$k}->[2], undef, $k) ];
355             };
356 2 50       5 $is_op->($k,'reduce') && do {
357             puke "reduce function '$k' requires an length 5 arrayref argument"
358 2 50 33     8 unless ref $$expr{$k} eq 'ARRAY' and @{$$expr{$k}} == 5;
  2         7  
359             return [ $k => [-thing => $$expr{$k}->[0]],
360             $do->($$expr{$k}->[1], undef, $k),
361             [-thing => $$expr{$k}->[2]],
362             $do->($$expr{$k}->[3], undef, $k),
363 2         36 $do->($$expr{$k}->[4], undef, $k)];
364             };
365 0         0 puke "Operator $k not expected";
366             }
367             elsif (ref($$expr{$k}) && ref($$expr{$k}) ne 'SCALAR') {
368             # $k is an LHS
369 38         545 return $do->($$expr{$k}, $k, undef);
370             }
371             else {
372             # implicit equality
373             return defined $$expr{$k} ?
374             [ $self->{config}{implicit_eq_op} => $k,
375 59 100       586 $do->($$expr{$k},undef,$self->{config}{implicit_eq_op}) ] :
376             [ -is_null => $k ];
377             }
378             }
379             #######
380             else {
381             # >1 hashpair
382 23         36 my @args;
383 23         45 for my $k (@k) {
384             # all keys are ops, or none is - otherwise barf
385 52 100 66     103 if ( $is_op->($k, 'infix_binary') ) {
    100          
    50          
386 6 50       22 puke "No LHS provided for implicit $$self{config}{hash_op}" unless defined $lhs;
387 6         33 push @args, $do->({$k => $$expr{$k}},$lhs);
388             }
389             elsif ( $is_op->($k, 'prefix') || $is_op->($k,'function') ) {
390 3         23 push @args, [ $k => $do->($$expr{$k},undef, $k) ];
391             }
392             elsif (!$is_op->($k)) {
393 43         333 push @args, $do->({$k => $$expr{$k}});
394             }
395             else {
396 0         0 puke "Problem handling operator '$k'";
397             }
398             }
399 23         99 return [ $self->{config}{hash_op} => @args ];
400             }
401             };
402             }
403 71         820 };
404 71         188 $ret = $do->($expr);
405 71         255 return $ret;
406             }
407              
408             # peel - recurse $args = [ -op, @args ] to create complete production
409             sub peel {
410 445     445 1 644 my ($self, $args) = @_;
411              
412 445 50 100     1385 if (!defined $args) {
    100          
    50          
413 0         0 return '';
414             }
415             elsif (!ref $args or blessed($args)) { # single literal argument
416 193         500 return $args;
417             }
418             elsif (ref $args eq 'ARRAY') {
419 252 50       473 return '' unless (@$args);
420 252         359 my $op = shift @$args;
421 252 50       529 puke "'$op' : unknown operator" unless $self->{dispatch}{$op};
422 252         369 my $expr = $self->_dispatch( $op, [map { $self->peel($_) } @$args] );
  389         640  
423 252 100       486 if (grep /\Q$op\E/, @{$type_table{infix_distributable}}) {
  252         2418  
424             # group
425 34         167 return "($expr)"
426             }
427             else {
428 218         703 return $expr;
429             }
430             }
431             else {
432 0         0 puke "Can only peel() arrayrefs, scalars or literals";
433             }
434             }
435              
436             ### writers
437              
438             sub infix_binary {
439 78     78 0 203 my ($self, $op, $args) = @_;
440 78 50 33     474 unless ($op and $args and !ref($op)
      33        
      33        
441             and ref($args) eq 'ARRAY'){
442 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
443             }
444 78 50       161 unless ( @$args == 2 ) {
445 0         0 puke "For $op, arg2 must have length 2";
446             }
447 78         164 return '('.join(" ", $$args[0], _write_op($op), $$args[1]).')';
448             }
449              
450 3     3 0 7 sub infix_listarg { infix_binary(@_) }
451              
452             sub infix_distributable {
453 35     35 0 73 my ($self, $op, $args) = @_;
454 35 50 33     241 unless ($op and $args and !ref($op)
      33        
      33        
455             and ref($args) eq 'ARRAY'){
456 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
457             }
458 35         78 $op = _write_op($op);
459 35         156 return join(" $op ", @$args);
460             }
461              
462             sub prefix {
463 12     12 0 25 my ($self, $op, $args) = @_;
464 12 50 33     113 unless ($op and $args and !ref($op)
      33        
      33        
465             and ref($args) eq 'ARRAY'){
466 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
467             }
468 12 50       32 unless (@$args == 1) {
469 0         0 puke "For $op, arg2 must have length 1"
470             }
471 12         25 return _write_op($op)." ".$$args[0];
472             }
473              
474             sub postfix {
475 6     6 0 12 my ($self, $op, $args) = @_;
476 6 50 33     63 unless ($op and $args and !ref($op)
      33        
      33        
477             and ref($args) eq 'ARRAY'){
478 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
479             }
480 6 50       20 unless (@$args == 1) {
481 0         0 puke "For $op, arg2 must have length 1"
482             }
483 6         29 return $$args[0]." "._write_op($op);
484             }
485              
486             sub function {
487 6     6 0 15 my ($self, $op, $args) = @_;
488 6 50 33     70 unless ($op and $args and !ref($op)
      33        
      33        
489             and ref($args) eq 'ARRAY'){
490 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
491             }
492 6         16 return _write_op($op).'('.join(',',@$args).')';
493             }
494              
495             sub predicate {
496 2     2 0 5 my ($self, $op, $args) = @_;
497 2 50 33     15 unless ($op and $args and !ref($op)
      33        
      33        
498             and ref($args) eq 'ARRAY'){
499 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
500             }
501 2 50       4 unless ( @$args == 3 ) {
502 0         0 puke "For $op, arg2 must have length 3";
503             }
504 2         5 return _write_op($op)."("."$$args[0] IN $$args[1] WHERE $$args[2]".")";
505             }
506              
507             sub extract {
508 0     0 0 0 my ($self, $op, $args) = @_;
509 0 0 0     0 unless ($op and $args and !ref($op)
      0        
      0        
510             and ref($args) eq 'ARRAY'){
511 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
512             }
513 0 0       0 unless ( @$args == 3 ) {
514 0         0 puke "For $op, arg2 must have length 3";
515             }
516 0         0 return _write_op($op)."("."$$args[0] IN $$args[1] | $$args[2]".")";
517             }
518              
519             sub reduce {
520 2     2 0 5 my ($self, $op, $args) = @_;
521 2 50 33     18 unless ($op and $args and !ref($op)
      33        
      33        
522             and ref($args) eq 'ARRAY'){
523 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
524             }
525 2 50       5 unless ( @$args == 5 ) {
526 0         0 puke "For $op, arg2 must have length 5";
527             }
528 2         4 return _write_op($op)."("."$$args[0] = $$args[1], $$args[2] IN $$args[3] | $$args[4]".")";
529             }
530              
531             # bind either:
532             # - pulls out literals, pushes them into {bind_values}, and replaces them
533             # with '?' in the produced statement (a la SQL:A), -or-
534             # - identifies named parameters in the expression and pushes these into
535             # {parameters}, leaving them in the produced statement
536             sub bind { # special
537 83     83 0 148 my ($self, $op, $args) = @_;
538 83 50 33     442 unless ($op and $args and !ref($op)
      33        
      33        
539             and ref($args) eq 'ARRAY'){
540 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
541             }
542 83 50       485 if ($$args[0] =~ $self->{config}{parameter_sigil}) {
543 0         0 push @{$self->{parameters}}, $$args[0];
  0         0  
544             }
545             else {
546 83         274 push @{$self->{bind_values}},
547 83 50       179 $self->{config}{anon_placeholder} ? $$args[0] :
548             $self->_quote_lit($$args[0]);
549             }
550             return $self->{config}{anon_placeholder} ?
551             $self->{config}{anon_placeholder} :
552 83 50       211 $self->_quote_lit($$args[0]);
553             }
554              
555             sub list { # special
556 5     5 0 20 my ($self, $op, $args) = @_;
557 5 50 33     50 unless ($op and $args and !ref($op)
      33        
      33        
558             and ref($args) eq 'ARRAY'){
559 0         0 puke "arg1 must be scalar, arg2 must be arrayref";
560             }
561 5         22 my ($l,$r) = split '',$self->{config}{list_braces};
562 5         21 return $l.join(',',@$args).$r;
563             }
564              
565             sub thing { # special
566 29     29 0 47 my ($self, $op, $args) = @_;
567 29         71 return join(' ',@$args);
568             }
569              
570             sub _write_op {
571 141     141   221 my ($op) = @_;
572 141         354 $op =~ s/^-//;
573 141         723 my $c = (caller(1))[3];
574 141 50       339 return '' if ($op eq '()');
575 141 100       320 return join(' ', map { ($c=~/infix|prefix|postfix/) ? uc $_ : $_ } split /_/,$op);
  155         1074  
576             }
577              
578             =head1 NAME
579              
580             Neo4j::Cypher::Abstract::Peeler - Parse Perl structures as expressions
581              
582             =head1 SYNOPSIS
583              
584             =head1 DESCRIPTION
585              
586             C allows the user to write L
587             Cypher|https://neo4j.com/docs/developer-manual/current/cypher/> query
588             language expressions as Perl data structures. The interpretation of
589             data structures follows L very closely, but attempts to
590             be more systematic and general.
591              
592             C only produces expressions, typically used as arguments to
593             C clauses. It is integrated into L,
594             which produces full Cypher statements.
595              
596             Like L, C translates scalars, scalar refs,
597             array refs, and hash refs syntactically to create expression
598             components such as functions and operators. The contents of the
599             scalars or references are generally operators or the arguments to
600             operators or functions.
601              
602             Contents of scalar references are always treated as literals and
603             inserted into the expression verbatim.
604              
605             =head2 Expressing Expressions
606              
607             =over
608              
609             =item * Functions
610              
611             Ordinary functions in Cypher are written as the name of the function
612             preceded by a dash. They can be expressed as follows:
613              
614             { -func => $arg }
615             [ -func => $arg ]
616             \"func($arg)"
617              
618             { -sin => $pi/2 }
619             # returns sin(/2)
620              
621             =item * Infix Operators
622              
623             Infix operators, like equality (C<=>), inequality (CE>),
624             binary operations (C<+,-,*,/>), and certain string operators
625             (C<-contains>, C<-starts_with>, C) are expressed as follows:
626              
627             { $expr1 => { $infix_op => $expr2 } }
628              
629             { 'n.name' => { '<>' => 'Fred' } }
630             # returns n.name <> "Fred"
631              
632             This may seem like overkill, but comes in handy for...
633              
634             =item * AND and OR
635              
636             C implements the L convention that hash refs
637             represent conditions joined by C and array refs represent
638             conditions joined by C. Key-value pairs and array value pairs are
639             interpreted as an implicit equalities to be ANDed or ORed.
640              
641             { $lhs1 => $rhs1, $lhs2 => $rhs2 }
642             { al => 'king', 'eddie' => 'prince', vicky => 'queen' }
643             # returns al = "king" AND eddie = "prince" AND vicky = "queen"
644              
645             [ $lhs1 => $rhs1, $lhs2 => $rhs2 ]
646             [ 'a.name' => 'Fred', 'a.name' => 'Barney']
647             # returns a.name = "Fred" OR a.name = "Barney"
648              
649             A single left-hand side can be "distributed" over a set of conditions,
650             with corresponding conjunction:
651              
652             { zzyxx => [ 'narf', 'boog', 'frelb' ] } # implicit equality, OR
653             # returns zzyxx = "narf" OR zzyxx = "boog" OR zzyxx = "frelb"
654             { zzyxx => { '<>' => 'narf', '<>' => 'boog' } } # explicit infix, AND
655             # returns zzyxx <> "narf" AND zzyxx <> "boog"
656             { zzyxx => [ '<>' => 'narf', -contains => 'boog' ] } # explicit infix, OR
657             # returns zzyxx <> "narf" OR zzyxx CONTAINS "boog"
658              
659             =item * Expressing null
660              
661             C can be used to express NULL mostly as in L so
662             that the following are equivalent
663              
664             { a.name => { '<>' => undef}, b.name => undef}
665             { -is_not_null => 'a.name', -is_null => 'b.name' }
666             # returns a.name IS NOT NULL AND b.name IS NULL
667              
668             =item * Predicates: -all, -any, -none, -single, -filter
669              
670             These Cypher functions have the form
671              
672             func(variable IN list WHERE predicate)
673              
674             To render these, provide an array ref of the three arguments in order:
675              
676             { -all => ['x', [1,2,3], {'x' => 3}] }
677             # returns all(x IN [1,2,3] WHERE x = 3)
678              
679             =item * List arguments
680              
681             For cypher expressions that accept lists (arrays in square brackets), use arrayrefs:
682              
683             { 'n.name' => { -in => ['fred','wilma','pebbles'] }}
684             # returns n.name IN ['fred','wilma','pebbles']
685              
686             =back
687              
688             =head2 Parameters and Bind Values
689              
690             Cypher parameters (which use the '$' sigil) may be included in
691             expressions (with the dollar sign appropriately escaped). These are
692             collected during parsing and can be reported in order with the the
693             C method.
694              
695             L automatically collects literal values and replaces
696             them with anonymous placeholders (C), returning an array of values
697             for binding in L. C will collect values and report them
698             with the C method. If the config key
699             C is set:
700              
701             $peeler->{config}{anon_placeholder} = '?'
702              
703             then C will also do the replacement in the final expression
704             production like L.
705              
706             The real reason to pay attention to literal values is to be able to
707             appropriately quote them in the final production. When
708             C is not set (default), then an attempt is made to
709             correctly quote string values and such.
710              
711             =head1 GUTS
712              
713             TBD
714              
715             =head2 The config hash
716              
717             The C object property C<{config}> is a hashref containing
718             various defaults to make Peeler output sound like Cypher. One could
719             customize it to make output sound like SQL (someday).
720              
721             Keys/values are as follows:
722              
723             bind => 1,
724             anon_placeholder => undef, # '?',
725             hash_op => '-and',
726             array_op => '-or',
727             list_braces => '[]',
728             ineq_op => '<>',
729             implicit_eq_op => '=',
730             quote_lit => "'",
731             esc_quote_lit => "\\",
732             parameter_sigil => qw/^([:$?])|({[^}]+}$)/,
733             quote_fld => undef,
734             safe_identifier => qw/[a-zA-Z_.]+/
735              
736             =head1 METHODS
737              
738             =over
739              
740             =item express()
741              
742             Canonize, then peel. Returns scalar string (the expression).
743              
744             =item canonize()
745              
746             Render SQL:A-like expression into a canonical lisp-like array
747             tree. Returns an arrayref.
748              
749             =item peel()
750              
751             Render a canonical tree as a Cypher string expression. Returns scalar
752             string.
753              
754             =item parameters()
755              
756             Get a list in order of all named parameters.
757              
758             =item bind_values()
759              
760             Get a list of bind values in order that were replaced by the anonymous
761             placeholder.
762              
763             =back
764              
765             =head1 SEE ALSO
766              
767             L, L.
768              
769             =head1 AUTHOR
770              
771             Mark A. Jensen
772             CPAN: MAJENSEN
773             majensen -at- cpan -dot- org
774              
775             =head1 LICENSE
776              
777             This software is provided for use under the terms of Perl itself.
778              
779             =head1 COPYRIGHT
780              
781             (c) 2017 Mark A. Jensen
782              
783             =cut
784              
785             1;