File Coverage

blib/lib/Neo4j/Cypher/Abstract.pm
Criterion Covered Total %
statement 122 168 72.6
branch 31 82 37.8
condition 23 78 29.4
subroutine 26 40 65.0
pod 19 26 73.0
total 221 394 56.0


line stmt bran cond sub pod time code
1             package Neo4j::Cypher::Abstract;
2 2     2   123128 use lib '../../../lib';
  2         21  
  2         9  
3 2     2   221 use base Exporter;
  2         3  
  2         159  
4 2     2   759 use Neo4j::Cypher::Pattern qw/pattern ptn/;
  2         5  
  2         116  
5 2     2   872 use Neo4j::Cypher::Abstract::Peeler;
  2         5  
  2         103  
6 2     2   14 use Scalar::Util qw/blessed/;
  2         4  
  2         80  
7 2     2   10 use Carp;
  2         4  
  2         135  
8             use overload
9             '""' => as_string,
10 2     2   11 'cmp' => sub { "$_[0]" cmp "$_[1]" };
  2     57   28  
  2         20  
  57         11292  
11 2     2   148 use strict;
  2         12  
  2         37  
12 2     2   8 use warnings;
  2         4  
  2         459  
13              
14              
15             our @EXPORT_OK = qw/cypher pattern ptn/;
16             our $AUTOLOAD;
17              
18             sub puke(@);
19             sub belch(@);
20              
21             our $VERSION='0.1003';
22              
23             # let an Abstract object keep its own stacks of clauses
24             # rather than clearing an existing Abstract object, get
25             # new objects from a factory = cypher()
26              
27             # create, create_unique, match, merge - patterns for args
28             # where, set - SQL::A like expression for argument (only assignments make
29             # sense for set)
30             # for_each - third arg is a cypher write query
31              
32             # 'as' - include in the string arguments : "n.name as name"
33              
34             our %clause_table = (
35             read => [qw/match optional_match where start/],
36             write => [qw/create merge set delete remove foreach
37             detach_delete
38             on_create on_match
39             create_unique/],
40             general => [qw/return order_by limit skip with unwind union
41             return_distinct with_distinct
42             call yield/],
43             hint => [qw/using_index using_scan using_join/],
44             load => [qw/load_csv load_csv_with_headers
45             using_periodic_commit/],
46             schema => [qw/create_constraint drop_constraint
47             create_index drop_index/],
48             modifier => [qw/skip limit order_by/]
49             );
50             our @all_clauses = ( map { @{$clause_table{$_}} } keys %clause_table );
51              
52             sub new {
53 64     64 0 98 my $class = shift;
54 64         106 my $self = {};
55 64         122 $self->{stack} = [];
56 64         250 bless $self, $class;
57             }
58              
59             sub cypher {
60 64     64 0 1462 Neo4j::Cypher::Abstract->new;
61             }
62 2     2 0 12 sub available_clauses {no warnings qw/once/; @__PACKAGE__::all_clauses }
  2     0   4  
  2         4329  
  0         0  
63              
64 0 0   0 0 0 sub bind_values { $_[0]->{bind_values} && @{$_[0]->{bind_values}} }
  0         0  
65 3 50   3 1 282 sub parameters { $_[0]->{parameters} && @{$_[0]->{parameters}} }
  3         22  
66              
67             # specials
68              
69             sub where {
70 22     22 1 33 my $self = shift;
71 22 50       43 puke "Need arg1 => expression" unless defined $_[0];
72 22         31 my $arg = $_[0];
73 22         40 $self->_add_clause('where',$arg);
74             }
75              
76 1     1 1 14 sub union { $_[0]->_add_clause('union') }
77 1     1 0 3 sub union_all { $_[0]->_add_clause('union_all') }
78              
79             sub order_by {
80 7     7 1 12 my $self = shift;
81 7 50       13 puke "Need arg1 => identifier" unless defined $_[0];
82 7         10 my @args;
83 7         14 while (my $a = shift) {
84 9 100 100     34 if ($_[0] and $_[0] =~ /^(?:de|a)sc$/i) {
85 2         8 push @args, "$a ".uc(shift());
86             }
87             else {
88 7         16 push @args, $a;
89             }
90             }
91 7         13 $self->_add_clause('order_by',@args);
92             }
93              
94             sub unwind {
95 2     2 1 6 my $self = shift;
96 2 50       8 puke "need arg1 => list expr" unless $_[0];
97 2 50 33     11 puke "need arg2 => list variable" unless ($_[1] && !ref($_[1]));
98 2         9 $self->_add_clause('unwind',$_[0],'AS',$_[1]);
99             }
100              
101             sub match {
102 49     49 1 68 my $self = shift;
103             # shortcut for a single node identifier, with labels
104 49 100 66     255 if (@_==1 and $_[0] =~ /^[a-z][a-z0-9_:]*$/i) {
105 24         76 $self->_add_clause('match',"($_[0])");
106             }
107             else {
108 25         72 $self->_add_clause('match',@_);
109             }
110             }
111              
112             sub create {
113 8     8 1 11 my $self = shift;
114             # shortcut for a single node identifier, with labels
115 8 100 66     42 if (@_==1 and $_[0] =~ /^[a-z][a-z0-9_:]*$/i) {
116 3         10 $self->_add_clause('create',"($_[0])");
117             }
118             else {
119 5         51 $self->_add_clause('create',@_);
120             }
121             }
122              
123             sub set {
124 10     10 1 12 my $self = shift;
125             # interpret a hashref argument as a set of key = value pairs
126 10 100 100     33 if (ref $_[0] eq 'HASH' && @_ == 1) {
127 5         8 $self->_add_clause('set', map { { $_ => $_[0]->{$_} } } sort keys %{$_[0]} )
  6         18  
  5         19  
128             }
129             else {
130 5         11 $self->_add_clause('set',@_);
131             }
132             }
133              
134             sub foreach {
135 1     1 1 2 my $self = shift;
136 1 50 33     16 puke "need arg1 => list variable" unless ($_[0] && !ref($_[0]));
137 1 50       4 puke "need arg2 => list expr" unless $_[1];
138 1 50       3 puke "need arg3 => cypher update stmt" unless $_[2];
139 1         6 $self->_add_clause('foreach', $_[0],'IN',$_[1],'|',$_[2]);
140             }
141              
142             sub load_csv {
143 0     0 1 0 my $self = shift;
144 0 0       0 puke "need arg1 => file location" unless $_[0];
145 0 0 0     0 puke "need arg2 => identifier" if (!defined $_[1] || ref $_[1]);
146 0         0 $self->_add_clause('load_csv','FROM',$_[0],'AS',$_[1]);
147             }
148              
149             sub load_csv_with_headers {
150 0     0 1 0 my $self = shift;
151 0 0       0 puke "need arg1 => file location" unless $_[0];
152 0 0 0     0 puke "need arg2 => identifier" if (!defined $_[1] || ref $_[1]);
153 0         0 $self->_add_clause('load_csv_with_headers','FROM',$_[0],'AS',$_[1]);
154             }
155              
156             #create_constraint_exist('node', 'label', 'property')
157              
158             sub create_constraint_exist {
159 0     0 1 0 my $self = shift;
160 0 0       0 puke "need arg1 => node/reln pattern" unless defined $_[0];
161 0 0 0     0 puke "need arg2 => label" if (!defined $_[1] || ref $_[1]);
162 0 0 0     0 puke "need arg2 => property" if (!defined $_[2] || ref $_[2]);
163 0         0 $self->_add_clause('create_constraint_on', "($_[0]:$_[1])", 'ASSERT',"exists($_[0].$_[2])");
164             }
165              
166             # create_constraint_unique('node', 'label', 'property')
167             sub create_constraint_unique {
168 0     0 1 0 my $self = shift;
169 0 0       0 puke "need arg1 => node/reln pattern" unless defined $_[0];
170 0 0 0     0 puke "need arg2 => label" if (!defined $_[1] || ref $_[1]);
171 0 0 0     0 puke "need arg2 => property" if (!defined $_[2] || ref $_[2]);
172 0         0 $self->_add_clause('create_constraint_on', "($_[0]:$_[1])", 'ASSERT',
173             "$_[0].$_[2]", 'IS UNIQUE');
174             }
175              
176             # create_index('label' => 'property')
177             sub create_index {
178 0     0 1 0 my $self = shift;
179 0 0 0     0 puke "need arg1 => node label" if (!defined $_[0] || ref $_[0]);
180 0 0 0     0 puke "need arg2 => node property" if (!defined $_[1] || ref $_[1]);
181 0         0 $self->_add_clause('create_index','ON',":$_[0]($_[1])");
182             }
183              
184             # drop_index('label'=>'property')
185             sub drop_index {
186 0     0 1 0 my $self = shift;
187 0 0 0     0 puke "need arg1 => node label" if (!defined $_[0] || ref $_[0]);
188 0 0 0     0 puke "need arg2 => node property" if (!defined $_[1] || ref $_[1]);
189 0         0 $self->_add_clause('drop_index','ON',":$_[0]($_[1])");
190             }
191              
192             # using_index('identifier', 'label', 'property')
193             sub using_index {
194 0     0 1 0 my $self = shift;
195 0 0 0     0 puke "need arg1 => identifier" if (!defined $_[0] || ref $_[0]);
196 0 0 0     0 puke "need arg2 => node label" if (!defined $_[1] || ref $_[1]);
197 0 0 0     0 puke "need arg3 => node property" if (!defined $_[2] || ref $_[2]);
198 0         0 $self->_add_clause('using_index',"$_[0]:$_[1]($_[2])");
199             }
200              
201             # using_scan('identifier' => 'label')
202             sub using_scan {
203 0     0 1 0 my $self = shift;
204 0 0 0     0 puke "need arg1 => identifier" if (!defined $_[0] || ref $_[0]);
205 0 0 0     0 puke "need arg2 => node label" if (!defined $_[1] || ref $_[1]);
206 0         0 $self->_add_clause('using_scan',"$_[0]:$_[1]");
207             }
208              
209             # using_join('identifier', ...)
210             sub using_join {
211 0     0 1 0 my $self = shift;
212 0 0 0     0 puke "need arg => identifier" if (!defined $_[0] || ref $_[0]);
213 0         0 $self->_add_clause('using_join', 'ON', join(',',@_));
214             }
215              
216             # everything winds up here
217             sub _add_clause {
218 175     175   228 my $self = shift;
219 175         211 my $clause = shift;
220 175         296 $self->{dirty} = 1;
221 175         210 my @clause;
222 175         266 push @clause, $clause;
223 175 100 100     753 if ( $clause =~ /^match|create|merge/ and
      100        
224             @_==1 and $_[0] =~ /^[a-z][a-z0-9_:]*$/i) {
225 2         5 push @clause, "($_[0])";
226             }
227             else {
228 173         276 for (@_) {
229 215 100 100     524 if (ref && !blessed($_)) {
230 32         97 my $plr = Neo4j::Cypher::Abstract::Peeler->new();
231 32         81 push @clause, $plr->express($_);
232             # kludge
233 32 100       84 if ($clause =~ /^set/) {
234             # removing enclosing parens from peel
235 10         37 $clause[-1] =~ s/^\s*\(//;
236 10         34 $clause[-1] =~ s/\)\s*$//;
237             }
238 32         43 push @{$self->{bind_values}}, $plr->bind_values;
  32         96  
239 32         46 push @{$self->{parameters}}, $plr->parameters;
  32         69  
240             }
241             else {
242 183         256 push @clause, $_;
243 183         296 my @parms = m/(\$[a-z][a-z0-9]*)/ig;
244 183         210 push @{$self->{parameters}}, @parms;
  183         367  
245             }
246             }
247             }
248 175 100       490 if ($clause =~ /^return|with|order|set|remove/) {
249             # group args in array so they are separated by commas
250 71         170 @clause = (shift @clause, [@clause]);
251             }
252 175         218 push @{$self->{stack}}, \@clause;
  175         291  
253 175         845 return $self;
254             }
255              
256             sub as_string {
257 70     70 1 422 my $self = shift;
258 70 100 66     190 return $self->{string} if ($self->{string} && !$self->{dirty});
259 63         105 undef $self->{dirty};
260 63         81 my @c;
261 63         74 for (@{$self->{stack}}) {
  63         132  
262 175         352 my ($kws, @arg) = @$_;
263 175         258 $kws =~ s/_/ /g;
264 175         227 for (@arg) {
265 180 100       438 $_ = join(',',@$_) if ref eq 'ARRAY';
266             }
267 175 100       272 if ($kws =~ /foreach/i) { #kludge for FOREACH
268 1         6 push @c, uc($kws)." (".join(' ',@arg).")";
269             }
270             else {
271 174         465 push @c, join(' ',uc $kws, @arg);
272             }
273             }
274 63         182 $self->{string} = join(' ',@c);
275 63         508 $self->{string} =~ s/(\s)+/$1/g;
276 63         857 return $self->{string};
277             }
278              
279             sub AUTOLOAD {
280 74     74   124 my $self = shift;
281 74         272 my ($method) = $AUTOLOAD =~ /.*::(.*)/;
282 74 50       1056 unless (grep /$method/, @all_clauses) {
283 0         0 puke "Unknown clause '$method'";
284             }
285 74         172 $self->_add_clause($method,@_);
286             }
287              
288             sub belch (@) {
289 0     0 0   my($func) = (caller(1))[3];
290 0           Carp::carp "[$func] Warning: ", @_;
291             }
292              
293             sub puke (@) {
294 0     0 0   my($func) = (caller(1))[3];
295 0           Carp::croak "[$func] Fatal: ", @_;
296             }
297              
298       0     sub DESTROY {}
299              
300             =head1 NAME
301              
302             Neo4j::Cypher::Abstract - Generate Cypher query statements
303              
304             =head1 SYNOPSIS
305              
306             =head1 DESCRIPTION
307              
308             When writing code to automate database queries, sometimes it is
309             convenient to use a wrapper that generates desired query strings. Then
310             the user can think conceptually and avoid having to remember precise
311             syntax or write and debug string manipulations. A good wrapper can
312             also allow the user to produce query statements dynamically, hide
313             dialect details, and may include some simple syntax
314             checking. C is an example of a widely-used wrapper for
315             SQL.
316              
317             The graph database L allows SQL-like
318             declarative queries through its query language
319             L. C
320             is a Cypher wrapper in the spirit of C that creates
321             very general Cypher productions in an intuitive, Perly way.
322              
323             =head2 Basic idea : stringing clauses together with method calls
324              
325             A clause is a portion of a complete query statement that plays a
326             specific functional role in the statement and is set off by one or
327             more reserved words. L
328             Cypher|https://neo4j.com/docs/developer-manual/current/cypher/clauses/>
329             include reading (e.g., MATCH), writing (CREATE), importing (LOAD CSV), and
330             schema (CREATE CONSTRAINT) clauses, among others. They have
331             arguments that define the clause's scope of action.
332              
333             L objects possess methods
334             for every Cypher clause. Each method adds its clause, with arguments,
335             to the object's internal queue. Every method returns the object
336             itself. When an object is rendered as a string, it concatenates its
337             clauses to yield the entire query statement.
338              
339             These features add up to the following idiom. Suppose we want to
340             render the Cypher statement
341              
342             MATCH (n:Users) WHERE n.name =~ 'Fred.*' RETURN n.manager
343              
344             In C, we do
345              
346             $s = Neo4j::Cypher::Abstract->new()->match('n:Users')
347             ->where("n.name =~ 'Fred.*'")->return('n.manager');
348             print "$s;\n"; # "" is overloaded by $s->as_string()
349              
350             Because you may create many such statements in a program, a short
351             alias for the constructor can be imported, and extra variable
352             assignments can be avoided.
353              
354             use Neo4j::Cypher::Abstract qw/cypher/;
355             use DBI;
356              
357             my $dbh = DBI->connect("dbi:Neo4p:http://127.0.0.1:7474;user=foo;pass=bar");
358             my $sth = $dbh->prepare(
359             cypher->match('n:Users')->where("n.name =~ 'Fred.*'")->return('n.manager')
360             );
361             $sth->execute();
362             ...
363              
364             =head2 Patterns
365              
366             L
367             are representations of subgraphs with constraints that are key
368             components of Cypher queries. They have their own syntax and are also
369             amenable to wrapping. In the example L
370             clauses together with method calls">, C uses a simple
371             built-in shortcut:
372              
373             $s->match('n:User') eq $s->match('(n:User)')
374              
375             where C<(n:User)> is the simple pattern for "all nodes with label
376             'User'". The module L handles
377             complex and arbitrary patterns. It is loaded automatically on C
378             Neo4j::Cypher::Abstract>. Abstract patterns are written in a similar
379             idiom as Cypher statements. They can be used anywhere a string is
380             allowed. For example:
381              
382             use Neo4j::Cypher::Abstract qw/cypher ptn/;
383              
384             ptn->N(':Person',{name=>'Oliver Stone'})->R("r>")->N('movie') eq
385             '(:Person {name:'Oliver Stone'})-[r]->(movie)'
386             $sth = $dbh->prepare(
387             cypher->match(ptn->N(':Person',{name=>'Oliver Stone'})->R("r>")->N('movie'))
388             ->return('type(r)')
389             );
390              
391             See L for a full description of how
392             to specify patterns.
393              
394             =head2 WHERE clauses
395              
396             As in SQL, Cypher has a WHERE clause that is used to filter returned
397             results. Rather than having to create custom strings for common WHERE
398             expressions, L provides an intuitive system for
399             constructing valid expressions from Perl data structures made up of
400             hash, array, and scalar references. L
401             contains a new implementation of the L expression
402             "compiler". If the argument to the C method (or any other
403             method, in fact) is an array or hash reference, it is interpreted as
404             an expression in L style. (The parser is a complete
405             reimplementation, so some idioms in that style may not result in
406             exactly the same productions.)
407              
408             For details on writing expressions, see
409             L.
410              
411             =head2 Parameters
412              
413             Parameters in Cypher are named, and given as alphanumeric tokens
414             prefixed (sadly) with '$'. The C object collects
415             these in the order they appear in the complete statement. The list of
416             parameters can be recovered with the C method.
417              
418             $c = cypher->match('n:Person')->return('n.name')
419             ->skip('$s')->limit('$l');
420             @p = $c->parameters; # @p is ('$s', '$l') /;
421              
422             =head1 METHODS
423              
424             =head2 Reading clauses
425              
426             =over
427              
428             =item match(@ptns)
429              
430             =item optional_match(@ptns)
431              
432             =item where($expr)
433              
434             =item start($ptn)
435              
436             =back
437              
438             =head2 Writing clauses
439              
440             =over
441              
442             =item create(@ptns), create_unique($ptn)
443              
444             =item merge(@ptns)
445              
446             =item foreach($running_var => $list, cypher->)
447              
448             =item set()
449              
450             =item delete(), detach_delete()
451              
452             =item on_create(), on_match()
453              
454             =back
455              
456             =head2 Modifiers
457              
458             =over
459              
460             =item limit($num)
461              
462             =item skip($num)
463              
464             =item order_by($identifier)
465              
466             =back
467              
468             =head2 General clauses
469              
470             =over
471              
472             =item return(@items), return_distinct(@items)
473              
474             =item with(@identifiers), with_distinct(@identifiers)
475              
476             =item unwind($list => $identifier)
477              
478             =item union()
479              
480             =item call()
481              
482             =item yield()
483              
484             =back
485              
486             =head2 Hinting
487              
488             =over
489              
490             =item using_index($index)
491              
492             =item using_scan()
493              
494             =item using_join($identifier)
495              
496             =back
497              
498             =head2 Loading
499              
500             =over
501              
502             =item load_csv($file => $identifier), load_csv_with_headers(...)
503              
504             =back
505              
506             =head2 Schema
507              
508             =over
509              
510             =item create_constraint_exist($node => $label, $property),create_constraint_unique($node => $label, $property)
511              
512             =item drop_constraint(...)
513              
514             =item create_index($label => $property), drop_index($label => $property)
515              
516             =back
517              
518             =head2 Utility Methods
519              
520             =over
521              
522             =item parameters()
523              
524             Return a list of statement parameters.
525              
526             =item as_string()
527              
528             Render the Cypher statement as a string. Overloads C<"">.
529              
530             =back
531              
532             =head1 SEE ALSO
533              
534             L, L, L, L, L
535              
536             =head1 AUTHOR
537              
538             Mark A. Jensen
539             CPAN: MAJENSEN
540             majensen -at- cpan -dot- org
541              
542             =head1 LICENSE
543              
544             This software is provided for use under the terms of Perl itself.
545              
546             =head1 COPYRIGHT
547              
548             (c) 2017 Mark A. Jensen
549              
550             =cut
551              
552             1;
553