File Coverage

blib/lib/REST/Neo4p.pm
Criterion Covered Total %
statement 133 284 46.8
branch 26 138 18.8
condition 14 46 30.4
subroutine 31 49 63.2
pod 15 22 68.1
total 219 539 40.6


line stmt bran cond sub pod time code
1 36     36   5318250 use v5.10;
  36         544  
2             package REST::Neo4p;
3 36     36   244 use Carp qw(croak carp);
  36         66  
  36         2861  
4 36     36   742 use lib '../../lib';
  36         763  
  36         235  
5 36     36   30860 use JSON;
  36         399213  
  36         197  
6 36     36   27282 use URI;
  36         162886  
  36         1162  
7 36     36   243 use URI::Escape;
  36         72  
  36         2102  
8 36     36   30106 use HTTP::Tiny;
  36         1895564  
  36         1685  
9 36     36   20309 use JSON::ize;
  36         401976  
  36         4672  
10 36     36   19172 use Neo4j::Driver 0.1801;
  36         3387261  
  36         1491  
11 36     36   19659 use REST::Neo4p::Agent;
  36         144  
  36         1368  
12 36     36   17884 use REST::Neo4p::Node;
  36         140  
  36         1295  
13 36     36   18152 use REST::Neo4p::Index;
  36         108  
  36         1150  
14 36     36   18819 use REST::Neo4p::Query;
  36         124  
  36         1401  
15 36     36   285 use REST::Neo4p::Exceptions;
  36         70  
  36         711  
16 36     36   196 use strict;
  36         66  
  36         728  
17 36     36   183 use warnings;
  36         67  
  36         1272  
18              
19             BEGIN {
20 36     36   129898 $REST::Neo4p::VERSION = '0.4003';
21             }
22              
23             our $CREATE_AUTO_ACCESSORS = 0;
24             our @HANDLES;
25             our $HANDLE = 0;
26             our $AGENT_MODULE = $ENV{REST_NEO4P_AGENT_MODULE} || 'LWP::UserAgent';
27              
28             my $json = JSON->new->allow_nonref(1)->utf8;
29              
30             $HANDLES[0]->{_q_endpoint} = 'cypher';
31              
32             sub set_handle {
33 10     10 0 18 my $class = shift;
34 10         18 my ($i) = @_;
35 10 50       27 REST::Neo4p::LocalException->throw("Nonexistent handle '$i'") unless defined $HANDLES[$i];
36 10         24 $HANDLE=$i;
37             }
38              
39             sub create_and_set_handle {
40 0     0 0 0 my $class = shift;
41 0         0 my @args = @_;
42 0         0 $HANDLE = @HANDLES;
43 0         0 $HANDLES[$HANDLE]->{_agent} = REST::Neo4p::Agent->new(agent_module => $AGENT_MODULE, @args);
44 0         0 $HANDLES[$HANDLE]->{_q_endpoint} = 'cypher';
45 0         0 return $HANDLE;
46             }
47              
48             sub disconnect_handle {
49 0     0 0 0 my $class = shift;
50 0         0 my ($i) = @_;
51 0 0       0 REST::Neo4p::LocalException->throw("Nonexistent handle '$i'") unless defined $HANDLES[$i];
52 0         0 delete $HANDLES[$i];
53 0         0 return 1;
54             }
55              
56             sub _set_transaction {
57 1     1   2 my $class = shift;
58 1         3 my ($tx) = @_;
59 1 50       6 die "Bad transaction id" unless $tx =~ /^[0-9]+$/;
60 1         5 return $HANDLES[$HANDLE]->{_transaction} = $tx;
61             }
62              
63             sub _transaction {
64 7     7   15 my $class = shift;
65 7         51 return $HANDLES[$HANDLE]->{_transaction};
66             }
67              
68             sub _tx_errors {
69 0     0   0 my $class = shift;
70 0         0 return $HANDLES[$HANDLE]->{_tx_errors};
71             }
72             sub _tx_results {
73 0     0   0 my $class = shift;
74 0         0 return $HANDLES[$HANDLE]->{_tx_results};
75             }
76              
77             sub _clear_transaction {
78 0     0   0 my $class = shift;
79 0         0 delete $HANDLES[$HANDLE]->{_transaction};
80             }
81              
82             sub _reset_transaction {
83 1     1   2 my $class = shift;
84 1         3 delete $HANDLES[$HANDLE]->{_tx_errors};
85 1         2 delete $HANDLES[$HANDLE]->{_tx_results};
86             }
87              
88             sub _set_autocommit {
89 0     0   0 my $class = shift;
90 0         0 return $HANDLES[$HANDLE]->{_q_endpoint} = 'cypher';
91             }
92              
93             sub _clear_autocommit {
94 0     0   0 my $class = shift;
95 0 0       0 if ($class->_check_version(2,0,0,2)) {
96 0         0 return $HANDLES[$HANDLE]->{_q_endpoint} = 'transaction';
97             }
98 0         0 return;
99             }
100              
101             sub q_endpoint {
102 11     11 0 2623 my $neo4p = shift;
103 11         45 return $HANDLES[$HANDLE]->{_q_endpoint};
104             }
105              
106             sub handle {
107 5     5 0 14 my $neo4p = shift;
108 5         68 return $HANDLE;
109             }
110              
111             sub agent {
112 4     4 1 33304 my $neo4p = shift;
113 4         29 my @args = @_;
114 4 100       26 unless (defined $HANDLES[$HANDLE]->{_agent}) {
115 3         19 eval {
116 3         100 $HANDLES[$HANDLE]->{_agent} = REST::Neo4p::Agent->new(agent_module => $AGENT_MODULE, @args);
117             };
118 3 50       124 if (my $e = REST::Neo4p::Exception->caught()) {
    50          
119             # TODO : handle different classes
120 0         0 $e->rethrow;
121             }
122             elsif ($e = Exception::Class->caught()) {
123 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
124             }
125             }
126 4         105 return $HANDLES[$HANDLE]->{_agent};
127             }
128              
129             # connect($host_and_port)
130             sub connect {
131 32     32 1 832225 my $neo4p = shift;
132 32         383 my ($server_address, $user, $pass) = @_;
133 32         1203 my $uri = URI->new($server_address);
134 32         65625 my ($u, $p);
135 32 50       894 $uri->userinfo and ($u, $p) = split(/:/,$uri->userinfo);
136 32   66     5832 $HANDLES[$HANDLE]->{_user} = $user // $u;
137 32   66     550 $HANDLES[$HANDLE]->{_pass} = $pass // $p;
138 32 50       367 REST::Neo4p::LocalException->throw("Server address not set\n") unless $server_address;
139 32         235 my ($major, $minor, $patch, $milestone);
140 32         212 eval {
141 32   66     949 ($major) = get_neo4j_version( $server_address, $user // $u, $pass // $p );
      66        
142             };
143 32 100       758606 if (my $e = Exception::Class->caught) {
144 30         2100 REST::Neo4p::CommException->throw("On version pre-check: $e");
145             }
146 2 100       26 if ($major >= 4) {
    50          
147 1 50       6 unless ($AGENT_MODULE eq 'Neo4j::Driver') {
148 1         16 warn "Neo4j version 4 or higher requires Neo4j::Driver as agent module; using this instead of $AGENT_MODULE";
149 1         72 $AGENT_MODULE = 'Neo4j::Driver';
150             }
151             }
152             elsif ($uri->scheme eq 'bolt') {
153 0 0       0 unless ($AGENT_MODULE eq 'Neo4j::Driver') {
154 0         0 warn "Bolt requires Neo4j::Driver as agent module; using this instead of $AGENT_MODULE";
155 0         0 $AGENT_MODULE = 'Neo4j::Driver';
156             }
157             }
158 2 50       63 $neo4p->agent->credentials($server_address,'Neo4j',$user,$pass) if defined $user;
159 2         12 my $connected = $neo4p->agent->connect($server_address);
160 2         39 return $HANDLES[$HANDLE]->{_connected} = $connected;
161             }
162              
163             sub get_neo4j_version {
164 31     31 0 31999 my ($url, $user, $pass) = @_;
165 31         1001 my $driver = Neo4j::Driver->new($url);
166 31 50 33     27634 $driver->basic_auth($user, $pass) if $user || $pass;
167 31         341 my $version = $driver->session->server->version;
168 0         0 my ($major, $minor, $patch, $milestone) =
169             $version =~ /^Neo4j\/(?:([0-9]+)\.)(?:([0-9]+)\.)?([0-9]+)?(?:-M([0-9]+))?/;
170 0 0       0 return wantarray ? ($major, $minor, $patch, $milestone) : $version;
171             }
172              
173             sub connected {
174 1     1 0 7 my $neo4p = shift;
175 1         82 return $HANDLES[$HANDLE]->{_connected};
176             }
177              
178             # $node = REST::Neo4p->get_node_by_id($id)
179             sub get_node_by_id {
180 0     0 1 0 my $neo4p = shift;
181 0         0 my ($id) = @_;
182 0         0 my $node;
183 0 0       0 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
184 0         0 eval {
185 0         0 $node = REST::Neo4p::Node->_entity_by_id($id);
186             };
187 0 0       0 if (my $e = REST::Neo4p::NotFoundException->caught()) {
    0          
188 0         0 return;
189             }
190             elsif ($e = Exception::Class->caught) {
191 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
192             }
193 0         0 return $node;
194             }
195              
196             sub get_nodes_by_label {
197 0     0 1 0 my $neo4p = shift;
198 0         0 my ($label,$prop, $value) = @_;
199 0 0       0 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
200 0         0 my $decoded_resp;
201 0 0       0 if ($value) {
202 0         0 $value = uri_escape($json->encode($value));
203             }
204              
205 0         0 eval {
206             # following line should work, but doesn't yet (self-discovery issue)
207             # $decoded_resp = $neo4p->agent->get_label($label, 'nodes');
208 0 0       0 $decoded_resp = $neo4p->agent->get_data('label',$label,'nodes',
209             $prop ? {$prop => $value} : () );
210 0         0 1;
211             };
212 0 0       0 if (my $e = REST::Neo4p::NotFoundException->caught()) {
    0          
213 0         0 return;
214             }
215             elsif ($e = Exception::Class->caught) {
216 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
217             }
218 0         0 my @ret;
219 0         0 foreach my $node_json (@$decoded_resp) {
220 0         0 push @ret, REST::Neo4p::Node->new_from_json_response($node_json);
221             }
222 0         0 return @ret;
223              
224             }
225              
226             sub get_all_labels {
227 0     0 1 0 my $neo4p = shift;
228 0 0       0 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
229 0         0 return @{ $neo4p->agent->get_data('labels') };
  0         0  
230             }
231              
232             sub get_relationship_by_id {
233 0     0 1 0 my $neo4p = shift;
234 0         0 my ($id) = @_;
235 0         0 my $relationship;
236 0 0       0 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
237 0         0 eval {
238 0         0 $relationship = REST::Neo4p::Relationship->_entity_by_id($id);
239             };
240 0 0       0 if (my $e = REST::Neo4p::NotFoundException->caught()) {
    0          
241 0         0 return;
242             }
243             elsif ($e = Exception::Class->caught) {
244 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
245             }
246 0         0 return $relationship;
247             }
248              
249             sub get_index_by_name {
250 0     0 1 0 my $neo4p = shift;
251 0         0 my ($name, $type) = @_;
252 0 0       0 if (grep /^$name$/, qw(node relationship)) {
253 0         0 my $a = $name;
254 0         0 $name = $type;
255 0         0 $type = $a;
256             }
257 0         0 my $idx;
258 0 0       0 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
259 0         0 eval {
260 0         0 $idx = REST::Neo4p::Index->_entity_by_id($name,$type);
261             };
262 0 0       0 if (my $e = REST::Neo4p::NotFoundException->caught()) {
    0          
263 0         0 return;
264             }
265             elsif ($e = Exception::Class->caught) {
266 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
267             }
268 0         0 return $idx;
269             }
270              
271             sub get_relationship_types {
272 0     0 1 0 my $neo4p = shift;
273 0 0       0 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
274 0         0 my $decoded_json;
275 0         0 eval {
276 0         0 $decoded_json = $neo4p->agent->get_relationship_types();
277             };
278 0         0 my $e;
279 0 0       0 if ($e = Exception::Class->caught('REST::Neo4p::Exception')) {
    0          
280             # TODO : handle different classes
281 0         0 $e->rethrow;
282             }
283             elsif ($@) {
284 0 0       0 ref $@ ? $@->rethrow : die $@;
285             }
286 0 0       0 return ref $decoded_json ? @$decoded_json : $decoded_json;
287             }
288              
289             sub get_indexes {
290 2     2 1 28295 my $neo4p = shift;
291 2         14 my ($type) = @_;
292 2 100       19 unless ($type) {
293 1         34 REST::Neo4p::LocalException->throw("Type argument (node or relationship) required\n");
294             }
295 1 50       20 REST::Neo4p::CommException->throw("Not connected\n") unless $neo4p->connected;
296 0         0 my $decoded_resp;
297 0         0 eval {
298 0         0 $decoded_resp = $neo4p->agent->get_data('index',$type);
299             };
300 0         0 my $e;
301 0 0       0 if ($e = Exception::Class->caught('REST::Neo4p::Exception')) {
    0          
302             # TODO : handle different classes
303 0         0 $e->rethrow;
304             }
305             elsif ($@) {
306 0 0       0 ref $@ ? $@->rethrow : die $@;
307             }
308 0         0 my @ret;
309             # this rest method returns a hash, not an array (as for relationships)
310 0         0 for (keys %$decoded_resp) {
311 0         0 push @ret, REST::Neo4p::Index->new_from_json_response($decoded_resp->{$_});
312             }
313 0         0 return @ret;
314             }
315              
316 0     0 1 0 sub get_node_indexes { shift->get_indexes('node',@_) }
317 0     0 1 0 sub get_relationship_indexes { shift->get_indexes('relationship',@_) }
318              
319             sub begin_work {
320 1     1 1 3 my $neo4p = shift;
321 1 50       5 unless ($neo4p->_check_version(2,0,0,2)) {
322 0         0 REST::Neo4p::VersionMismatchException->throw("Transactions are not available in Neo4j server version < 2.0.0-M02\n");
323             }
324 1 50       6 if ($neo4p->_transaction) {
325 0         0 REST::Neo4p::TxException->throw("Transaction already initiated\n");
326             }
327 1         4 $HANDLES[$HANDLE]->{_old_endpoint} = $HANDLES[$HANDLE]->{_q_endpoint};
328 1         3 $HANDLES[$HANDLE]->{_q_endpoint} = 'transaction';
329 1         4 $neo4p->_reset_transaction;
330 1         2 my $resp;
331 1         2 eval {
332 1         4 $resp = $neo4p->agent->post_transaction([]);
333             REST::Neo4p::Neo4jException->throw($resp->{errors}->[0]->{message})
334 1 50       160 if @{$resp->{errors}};
  1         5  
335             };
336 1 50       4 if (my $e = REST::Neo4p::Exception->caught()) {
    50          
337             # TODO : handle different classes
338 0         0 $e->rethrow;
339             }
340             elsif ($e = Exception::Class->caught()) {
341 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
342             }
343 1         23 my ($tx) = $resp->{commit} =~ m|.*/([0-9]+)/commit$|;
344 1         5 return REST::Neo4p->_set_transaction($tx);
345             }
346              
347             sub commit {
348 0     0 1 0 my $neo4p = shift;
349 0 0       0 unless ($neo4p->_check_version(2,0,0,2)) {
350 0         0 REST::Neo4p::VersionMismatchException->throw("Transactions are not available in Neo4j server version < 2.0.0-M02\n");
351             }
352 0 0       0 return 1 if ($neo4p->q_endpoint eq 'cypher'); # noop, server autocommited
353 0 0       0 unless ($neo4p->q_endpoint eq 'transaction') {
354 0         0 REST::Neo4p::TxException->throw("Unknown REST endpoint '".$neo4p->q_endpoint."'\n");
355             }
356 0         0 $HANDLES[$HANDLE]->{_q_endpoint} = delete $HANDLES[$HANDLE]->{_old_endpoint};
357 0         0 my $resp;
358 0         0 eval {
359 0         0 $resp = $neo4p->agent->post_transaction(
360             [$neo4p->_transaction,'commit']
361             );
362             };
363 0 0       0 if (my $e = REST::Neo4p::Exception->caught()) {
    0          
364             # TODO : handle different classes
365 0         0 $e->rethrow;
366             }
367             elsif ($e = Exception::Class->caught()) {
368 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
369             }
370 0         0 $neo4p->_clear_transaction;
371 0         0 $HANDLES[$HANDLE]->{_tx_results} = $resp->{results};
372 0         0 $HANDLES[$HANDLE]->{_tx_errors} = $resp->{errors};
373 0         0 return !(scalar @{$resp->{errors}});
  0         0  
374             }
375              
376             sub rollback {
377 0     0 1 0 my $neo4p = shift;
378 0 0       0 unless ($neo4p->_check_version(2,0,0,2)) {
379 0         0 REST::Neo4p::VersionMismatchException->throw("Transactions are not available in Neo4j server version < 2.0.0-M02\n");
380             }
381 0 0       0 if ($neo4p->q_endpoint eq 'cypher') {
382 0         0 REST::Neo4p::TxException->throw("Rollback attempted in auto-commit mode\n");
383             }
384 0 0       0 unless ($neo4p->q_endpoint eq 'transaction') {
385 0         0 REST::Neo4p::TxException->throw("Unknown REST endpoint '".$neo4p->q_endpoint."'\n");
386             }
387 0         0 $HANDLES[$HANDLE]->{_q_endpoint} = delete $HANDLES[$HANDLE]->{_old_endpoint}; eval {
  0         0  
388 0         0 $neo4p->agent->delete_transaction($neo4p->_transaction);
389             };
390 0 0       0 if (my $e = REST::Neo4p::Exception->caught()) {
    0          
391             # TODO : handle different classes
392 0         0 $e->rethrow;
393             }
394             elsif ($e = Exception::Class->caught()) {
395 0 0 0     0 (ref $e && $e->can("rethrow")) ? $e->rethrow : die $e;
396             }
397 0         0 $neo4p->_reset_transaction;
398 0         0 return $neo4p->_clear_transaction;
399             }
400              
401             sub neo4j_version {
402 1     1 1 3 my $neo4p = shift;
403 1         10 my $v = my $a = $neo4p->agent->{_actions}{neo4j_version};
404 1 50       7 return unless defined $v;
405 0         0 my ($major, $minor, $patch, $milestone) =
406             $a =~ /^(?:([0-9]+)\.)(?:([0-9]+)\.)?([0-9]+)?(?:-M([0-9]+))?/;
407 0 0       0 wantarray ? ($major,$minor,$patch,$milestone) : $v;
408             }
409              
410             sub _check_version {
411 2     2   2290 my $neo4p = shift;
412 2         7 my ($major, $minor, $patch, $milestone) = @_;
413 2         13 my ($M,$m,$p,$s) = $neo4p->neo4j_version;
414 2         10 my ($current, $requested);
415 2         5 $current = $requested = 0;
416 2         5 for ($M,$m,$p) {
417 6   100     29 $current += $_||0;
418 6         14 $current *= 100;
419             }
420 2         11 for ($major,$minor,$patch) {
421 6   100     19 $requested += $_||0;
422 6         21 $requested *= 100;
423             }
424 2 50 33     19 if (defined $milestone && defined $s) {
425 0         0 $current += $s;
426 0         0 $requested += $milestone;
427             }
428 2         11 return $requested <= $current;
429             }
430              
431             sub DESTROY {
432 0     0     my $self = shift;
433 0           delete $HANDLES[$self->handle];
434 0           return;
435             }
436              
437             =head1 NAME
438              
439             REST::Neo4p - Perl object bindings for a Neo4j database
440              
441             =head1 SYNOPSIS
442              
443             use REST::Neo4p;
444             REST::Neo4p->connect('http://127.0.0.1:7474');
445             $i = REST::Neo4p::Index->new('node', 'my_node_index');
446             $i->add_entry(REST::Neo4p::Node->new({ name => 'Fred Rogers' }),
447             guy => 'Fred Rogers');
448             $index = REST::Neo4p->get_index_by_name('my_node_index','node');
449             ($my_node) = $index->find_entries('guy' => 'Fred Rogers');
450             $new_neighbor = REST::Neo4p::Node->new({'name' => 'Donkey Hoty'});
451             $my_reln = $my_node->relate_to($new_neighbor, 'neighbor');
452              
453             $query = REST::Neo4p::Query->new("MATCH p = (n)-[]->()
454             WHERE id(n) = \$id
455             RETURN p", { id => $my_node->id });
456             $query->execute;
457             $path = $query->fetch->[0];
458             @path_nodes = $path->nodes;
459             @path_rels = $path->relationships;
460              
461             Batch processing (see L for more)
462              
463             I
464              
465             #!perl
466             # loader...
467             use REST::Neo4p;
468             use REST::Neo4p::Batch;
469            
470             open $f, shift() or die $!;
471             batch {
472             while (<$f>) {
473             chomp;
474             ($name, $value) = split /\t/;
475             REST::Neo4p::Node->new({name => $name, value => $value});
476             } 'discard_objs';
477             exit(0);
478              
479             =head1 DESCRIPTION
480              
481             REST::Neo4p provides a Perl 5 object framework for accessing and
482             manipulating a L graph database server via the
483             Neo4j REST API. Its goals are
484              
485             (1) to make the API as transparent as possible, allowing the user to
486             work exclusively with Perl objects, and
487              
488             (2) to exploit the API's self-discovery mechanisms, avoiding as much
489             as possible internal hard-coding of URLs.
490              
491             B: The REST API and the "cypher endpoint" are no
492             longer found in Neo4j servers after version 3.5. Never fear: the
493             C user agent, based on AJNN's L,
494             emulates both of these deprecated endpoints for REST::Neo4p. The goal
495             is that REST::Neo4p will plug and play with version 4.0+. Be sure to
496             report any bugs.
497              
498             Neo4j entities are represented by corresponding classes:
499              
500             =over
501              
502             =item *
503              
504             Nodes : L
505              
506             =item *
507              
508             Relationships : L
509              
510             =item *
511              
512             Indexes : L
513              
514             =back
515              
516             Actions on class instances have a corresponding effect on the database
517             (i.e., REST::Neo4p approximates an ORM).
518              
519             The class L provides a DBIesqe Cypher query facility.
520             (And see also L.)
521              
522             =head2 Property Auto-accessors
523              
524             Depending on the application, it may be natural to think of properties
525             as fields of your nodes and relationships. To create accessors named
526             for the entity properties, set
527              
528             $REST::Neo4p::CREATE_AUTO_ACCESSORS = 1;
529              
530             Then, when L is used
531             to first create and set a property, accessors will be created on the
532             class:
533              
534             $node1->set_property({ flavor => 'strange', spin => -0.5 });
535             printf "Quark has flavor %s\n", $node1->flavor;
536             $node1->set_spin(0.5);
537              
538             If your point of reference is the database, rather than the objects,
539             auto-accessors may be confusing, since once the accessor is created
540             for the class, it will exist for all future instances:
541              
542             print "Yes I can!\n" if REST::Neo4p::Node->new()->can('flavor');
543              
544             but there is no fundamental reason why new nodes or relationships must
545             have the property (it is NoSQL, after all). Therefore this is a choice
546             for you to make; the default is I auto-accessors.
547              
548             =head2 Application-level constraints
549              
550             L provides a flexible means for creating,
551             enforcing, serializing and loading property and relationship
552             constraints on your database through REST::Neo4p. It allows you, for
553             example, to specify "kinds" of nodes based on their properties,
554             constrain properties and the values of properties for those nodes, and
555             then specify allowable relationships between kinds of nodes.
556              
557             Constraints can be enforced automatically, causing exceptions to be
558             thrown
559             when constraints are violated. Alternatively, you can use
560             validation functions to test properties and relationships, including
561             those already present in the database.
562              
563             This is a mixin that is not Id automatically by REST::Neo4p. For
564             details and examples, see L and
565             L.
566              
567             =head2 Server-side constraints (Neo4j server version 2.0.1+)
568              
569             Neo4j L<"schema" constraints|http://docs.neo4j.org/chunked/stable/cypher-schema.html>
570             based on labels can be manipulated via REST using
571             L.
572              
573             =head1 USER AGENT
574              
575             The backend user agent can be selected by setting the package variable
576             C<$REST::Neo4p::AGENT_MODULE> to one of the following
577              
578             LWP::UserAgent
579             Mojo::UserAgent
580             HTTP::Thin
581             Neo4j::Driver
582              
583             The L created will be a subclass of the selected
584             backend agent. It can be accessed with L.
585              
586             The initial value of C<$REST::Neo4p::AGENT_MODULE> will be the value
587             of the environment variable C or
588             C by default.
589              
590             If your Neo4j database is version 4.0 or greater, C
591             will be used automatically and a warning will ensue if this overrides
592             a different choice.
593              
594             =head1 CLASS METHODS
595              
596             =over
597              
598             =item connect()
599              
600             REST::Neo4p->connect( $server );
601             REST::Neo4p->connect( $server, $user, $pass );
602              
603             =item agent()
604              
605             REST::Neo4p->agent->credentials( $server, 'Neo4j', $user, $pass);
606             REST::Neo4p->connect($server);
607              
608             Returns the underlying L object.
609              
610             =item neo4j_version()
611              
612             $version_string = REST::Neo4p->neo4j_version;
613             ($major, $minor, $patch, $milestone) = REST::Neo4p->neo4j_version;
614              
615             Returns the server's neo4j version string/components, or undef if not connected.
616              
617             =item get_node_by_id()
618              
619             $node = REST::Neo4p->get_node_by_id( $id );
620              
621             Returns false if node C<$id> does not exist in database.
622              
623             =item get_relationship_by_id()
624              
625             $relationship = REST::Neo4p->get_relationship_by_id( $id );
626              
627             Returns false if relationship C<$id> does not exist in database.
628              
629             =item get_index_by_name()
630              
631             $node_index = REST::Neo4p->get_index_by_name( $name, 'node' );
632             $relationship_index = REST::Neo4p->get_index_by_name( $name, 'relationship' );
633              
634             Returns false if index C<$name> does not exist in database.
635              
636             =item get_relationship_types()
637              
638             @all_relationship_types = REST::Neo4p->get_relationship_types;
639              
640             =item get_indexes(), get_node_indexes(), get_relationship_indexes()
641              
642             @all_indexes = REST::Neo4p->get_indexes;
643             @node_indexes = REST::Neo4p->get_node_indexes;
644             @relationship_indexes = REST::Neo4p->get_relationship_indexes;
645              
646             =back
647              
648             =head2 Label Support (Neo4j version 2.0+)
649              
650             =over
651              
652             =item get_nodes_by_label()
653              
654             @nodes = REST::Neo4p->get_nodes_by_label( $label );
655             @nodes = REST::Neo4p->get_nodes_by_label($label, $property => $value );
656              
657             Returns false if no nodes with given label in database.
658              
659             =item get_all_labels()
660              
661             @graph_labels = REST::Neo4p->get_all_labels;
662              
663             =back
664              
665             =head2 Transaction Support (Neo4j version 2.0+)
666              
667             Initiate, commit, or rollback L in transactions.
668              
669             =over
670              
671             =item begin_work()
672              
673             =item commit()
674              
675             =item rollback()
676            
677             $q = REST::Neo4p::Query->new(
678             'match (n)-[r:pal]->(m) where id(n)=0 create r'
679             );
680             $r = REST::Neo4p::Query->new(
681             'match (n)-[r:pal]->(u) where id(n)=0 merge u'
682             );
683             REST::Neo4p->begin_work;
684             $q->execute;
685             $r->execute;
686             if ($q->err || $r->err) {
687             REST::Neo4p->rollback;
688             }
689             else {
690             REST::Neo4p->commit;
691             $results = REST::Neo4p->_tx_results;
692             unless (REST::Neo4p->_tx_errors) {
693             print 'all queries successful';
694             }
695             }
696              
697             =item _tx_results(), _tx_errors()
698              
699             These fields contain decoded JSON responses from the server following
700             a commit. C<_tx_errors> is an arrayref of statement errors during
701             commit. C<_tx_results> is an arrayref of columns-data hashes as
702             described at
703             L.
704              
705             These fields are cleared by C and C.
706              
707             =back
708              
709             =head1 SEE ALSO
710              
711             L,L,L,
712             L, L, L,
713             L,L, L.
714              
715             =head1 AUTHOR
716              
717             Mark A. Jensen
718             CPAN ID: MAJENSEN
719             majensen -at- cpan -dot- org
720              
721             =head1 LICENSE
722              
723             Copyright (c) 2012-2022 Mark A. Jensen. This program is free software; you
724             can redistribute it and/or modify it under the same terms as Perl
725             itself.
726              
727             =cut
728              
729             1;
730