File Coverage

blib/lib/REST/Neo4p.pm
Criterion Covered Total %
statement 142 290 48.9
branch 32 142 22.5
condition 9 37 24.3
subroutine 30 48 62.5
pod 15 22 68.1
total 228 539 42.3


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