File Coverage

blib/lib/REST/Neo4p.pm
Criterion Covered Total %
statement 145 293 49.4
branch 32 142 22.5
condition 9 37 24.3
subroutine 31 49 63.2
pod 15 22 68.1
total 232 543 42.7


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