From e7ca25a311db1d861d03d6749fb293e92fbedad9 Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Thu, 5 Dec 2024 02:40:35 +0100 Subject: [PATCH 1/8] Add support for Neo4j::Types Neo4j::Driver 1.02 no longer reblesses values received via Neo4j::Bolt. Like before, the returned values are still Neo4j::Types implementations, so their interface doesn't actually change. But the package name of such values changes from Neo4j::Driver::Type::* to Neo4j::Bolt::*, which trips up some checks in REST::Neo4p that expect to see "Driver" in the package name. Original discussion: https://github.com/majensen/perlbolt/pull/37 --- Build.PL | 2 +- lib/REST/Neo4p/Entity.pm | 3 ++- lib/REST/Neo4p/Node.pm | 6 +++--- lib/REST/Neo4p/Path.pm | 3 ++- lib/REST/Neo4p/Query.pm | 17 ++++++++++------- lib/REST/Neo4p/Relationship.pm | 6 +++--- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Build.PL b/Build.PL index d98a972..6e30372 100644 --- a/Build.PL +++ b/Build.PL @@ -78,7 +78,7 @@ my $build = $class->new 'experimental' => 0, 'MIME::Base64' => 0, perl => 5.010001, - 'Neo4j::Driver' => '0.19', + 'Neo4j::Driver' => '0.21', # Neo4j::Types }, recommends => { 'Mojo::UserAgent' => 0, diff --git a/lib/REST/Neo4p/Entity.pm b/lib/REST/Neo4p/Entity.pm index 2830371..737438f 100644 --- a/lib/REST/Neo4p/Entity.pm +++ b/lib/REST/Neo4p/Entity.pm @@ -4,6 +4,7 @@ package REST::Neo4p::Entity; use REST::Neo4p::Exceptions; use Carp qw(croak carp); use JSON; +use Scalar::Util qw(blessed); use URI::Escape; use strict; use warnings; @@ -65,7 +66,7 @@ sub new_from_json_response { unless (defined $decoded_resp) { REST::Neo4p::LocalException->throw("new_from_json_response() called with undef argument\n"); } - my $is_json = !(ref($decoded_resp) =~ /Neo4j::Driver/); + my $is_json = ! blessed $decoded_resp; # blessed via Neo4j::Driver unless ($ENTITY_TABLE->{$entity_type}{_actions} || !$is_json) { # capture the url suffix patterns for the entity actions: for (keys %$decoded_resp) { diff --git a/lib/REST/Neo4p/Node.pm b/lib/REST/Neo4p/Node.pm index ee54230..180b5da 100644 --- a/lib/REST/Neo4p/Node.pm +++ b/lib/REST/Neo4p/Node.pm @@ -222,8 +222,8 @@ sub simple_from_json_response { my $class = shift; my ($decoded_resp) = @_; my $ret; - for (ref $decoded_resp) { - /HASH/ && do { + for ($decoded_resp) { + ref eq 'HASH' && do { # node id ($ret->{_node}) = $decoded_resp->{self} =~ m{.*/([0-9]+)$}; # node properties @@ -235,7 +235,7 @@ sub simple_from_json_response { } last; }; - /Driver/ && do { + $_->isa('Neo4j::Types::Node') && do { # via Neo4j::Driver $ret->{_node} = $decoded_resp->id; $ret->{$_} = $decoded_resp->properties->{$_} for keys %{$decoded_resp->properties}; last; diff --git a/lib/REST/Neo4p/Path.pm b/lib/REST/Neo4p/Path.pm index ef4444d..258f55f 100644 --- a/lib/REST/Neo4p/Path.pm +++ b/lib/REST/Neo4p/Path.pm @@ -2,6 +2,7 @@ package REST::Neo4p::Path; use REST::Neo4p::Exceptions; use Carp qw(croak carp); +use Scalar::Util qw(blessed); use strict; use warnings; BEGIN { @@ -16,7 +17,7 @@ sub new { sub new_from_json_response { my $class = shift; my ($decoded_resp) = @_; - return $class->new_from_driver_obj(@_) if (ref($decoded_resp) =~ /Neo4j::Driver/); + return $class->new_from_driver_obj(@_) if blessed $decoded_resp; REST::Neo4p::LocalException->throw("Arg does not describe a Neo4j path response\n") unless $decoded_resp->{start} && $decoded_resp->{end} && $decoded_resp->{relationships} && $decoded_resp->{nodes}; my $obj = bless {}, $class; $obj->{_length} = $decoded_resp->{length}; diff --git a/lib/REST/Neo4p/Query.pm b/lib/REST/Neo4p/Query.pm index c29a098..f7b9804 100644 --- a/lib/REST/Neo4p/Query.pm +++ b/lib/REST/Neo4p/Query.pm @@ -5,6 +5,7 @@ use REST::Neo4p::Exceptions; use JSON::XS; use REST::Neo4p::ParseStream; use HOP::Stream qw/drop/; +use Scalar::Util qw(blessed); use Tie::IxHash; use File::Temp qw(:seekable); use Carp qw(croak carp); @@ -177,22 +178,24 @@ sub _wrap_statement_result { for (my $i=0;$i<$n;$i++) { my $elt = $rec->get($i); my $cvt = sub { - return $_[0] unless ref($_[0]) =~ /Driver/; - my ($type) = ref($_[0]) =~ /::([^:]+)$/; - my $cls = "REST::Neo4p::$type"; + return $_[0] unless blessed $_[0]; + my $cls = $_[0]->isa('Neo4j::Types::Node') ? 'REST::Neo4p::Node' + : $_[0]->isa('Neo4j::Types::Relationship') ? 'REST::Neo4p::Relationship' + : $_[0]->isa('Neo4j::Types::Path') ? 'REST::Neo4p::Path' + : undef or return $_[0]; # spatial/temporal values return $as_object ? $cls->new_from_json_response($_[0]) : $cls->simple_from_json_response($_[0]); }; - for (ref($elt)) { - /Driver/ && do { + for ($elt) { + blessed $_ && do { # Neo4j::Types::*, via Neo4j::Driver $elt = $cvt->($elt); }; - /HASH/ && do { + ref eq 'HASH' && do { for (keys %$elt) { $elt->{$_} = $cvt->($elt->{$_}) } }; - /ARRAY/ && do { + ref eq 'ARRAY' && do { for (@$elt) { $_ = $cvt->($_); } diff --git a/lib/REST/Neo4p/Relationship.pm b/lib/REST/Neo4p/Relationship.pm index 4feb419..a2cdc3e 100644 --- a/lib/REST/Neo4p/Relationship.pm +++ b/lib/REST/Neo4p/Relationship.pm @@ -49,8 +49,8 @@ sub simple_from_json_response { my $class = shift; my ($decoded_resp) = @_; my $ret; - for (ref $decoded_resp) { - /HASH/ && do { + for ($decoded_resp) { + ref eq 'HASH' && do { # reln id ($ret->{_relationship}) = $decoded_resp->{self} =~ m{.*/([0-9]+)$}; # reln type @@ -62,7 +62,7 @@ sub simple_from_json_response { ($ret->{_end}) = $decoded_resp->{end} =~ m{.*/([0-9]+)$}; last; }; - /Driver/ && do { + $_->isa('Neo4j::Types::Relationship') && do { # via Neo4j::Driver $ret->{_relationship} = $decoded_resp->id; $ret->{_type} = $decoded_resp->type; $ret->{$_} = $decoded_resp->properties->{$_} for keys %{$decoded_resp->properties}; From 985656d04644dbcabdb5d9dc606b1029ad77eb7a Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Sun, 17 Nov 2024 08:46:58 +0100 Subject: [PATCH 2/8] Force statement execution before execution error check It's necessary to call a method on the result within the try block. Any method will do; has_next() is one of the cheapest ones to call. Resolves #28. --- lib/REST/Neo4p/Agent/Neo4j/Driver.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/REST/Neo4p/Agent/Neo4j/Driver.pm b/lib/REST/Neo4p/Agent/Neo4j/Driver.pm index 31605b3..452a118 100644 --- a/lib/REST/Neo4p/Agent/Neo4j/Driver.pm +++ b/lib/REST/Neo4p/Agent/Neo4j/Driver.pm @@ -229,6 +229,7 @@ sub run_in_session { $params = {} unless defined $params; try { $self->{_last_result} = $self->session->run($qry, $params); + $self->{_last_result}->has_next; # Make sure the query has executed and any errors have been thrown } catch { $self->{_last_errors} = $_; }; @@ -243,6 +244,7 @@ sub run_in_transaction { $params = {} unless defined $params; try { $self->{_last_result} = $tx->run($qry, $params); + $self->{_last_result}->has_next; # Make sure the query has executed and any errors have been thrown } catch { $self->{_last_errors} = $_; }; From 2455228f75f384fb5bf5f8481d5312e24b5a0c0e Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Fri, 29 Nov 2024 00:28:27 +0100 Subject: [PATCH 3/8] Avoid warnings for Neo4j 4.3 version() deprecation https://metacpan.org/release/AJNN/Neo4j-Driver-1.01-TRIAL/view/lib/Neo4j/Driver/ServerInfo.pm#agent --- Build.PL | 2 +- lib/REST/Neo4p.pm | 4 ++-- lib/REST/Neo4p/Agent/Neo4j/Driver.pm | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Build.PL b/Build.PL index 6e30372..df1adc9 100644 --- a/Build.PL +++ b/Build.PL @@ -78,7 +78,7 @@ my $build = $class->new 'experimental' => 0, 'MIME::Base64' => 0, perl => 5.010001, - 'Neo4j::Driver' => '0.21', # Neo4j::Types + 'Neo4j::Driver' => '0.26', # ServerInfo->agent }, recommends => { 'Mojo::UserAgent' => 0, diff --git a/lib/REST/Neo4p.pm b/lib/REST/Neo4p.pm index 02ba816..6c30b83 100644 --- a/lib/REST/Neo4p.pm +++ b/lib/REST/Neo4p.pm @@ -7,7 +7,7 @@ use URI; use URI::Escape; use HTTP::Tiny; use JSON::ize; -use Neo4j::Driver 0.1801; +use Neo4j::Driver 0.26; use REST::Neo4p::Agent; use REST::Neo4p::Node; use REST::Neo4p::Index; @@ -164,7 +164,7 @@ sub get_neo4j_version { my ($url, $user, $pass) = @_; my $driver = Neo4j::Driver->new($url); $driver->basic_auth($user, $pass) if $user || $pass; - my $version = $driver->session->server->version; + my $version = $driver->session->server->agent; my ($major, $minor, $patch, $milestone) = $version =~ /^Neo4j\/(?:([0-9]+)\.)(?:([0-9]+)\.)?([0-9]+)?(?:-M([0-9]+))?/; return wantarray ? ($major, $minor, $patch, $milestone) : $version; diff --git a/lib/REST/Neo4p/Agent/Neo4j/Driver.pm b/lib/REST/Neo4p/Agent/Neo4j/Driver.pm index 452a118..29b5d0c 100644 --- a/lib/REST/Neo4p/Agent/Neo4j/Driver.pm +++ b/lib/REST/Neo4p/Agent/Neo4j/Driver.pm @@ -2,7 +2,7 @@ package REST::Neo4p::Agent::Neo4j::Driver; use v5.10; use lib '../../../../../lib'; # testing use base qw/REST::Neo4p::Agent/; -use Neo4j::Driver 0.1803; +use Neo4j::Driver 0.26; use JSON::ize; use REST::Neo4p::Agent::Neo4j::DriverActions; use REST::Neo4p::Exceptions; @@ -176,7 +176,7 @@ sub connect { for (my $i = $REST::Neo4p::Agent::RQ_RETRIES; $i>0; $i--) { my $f; try { - my $version = $drv->session->server->version; + my $version = $drv->session->server->agent; $version =~ s|^\S+/||; # server version strings look like "Neo4j/3.2.1" $self->{_actions}{neo4j_version} = $version or die "Can't find neo4j_version from server"; From f0a4fe66e7c62c0577667a83a965426aa9d4d490 Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Sun, 17 Nov 2024 08:36:48 +0100 Subject: [PATCH 4/8] Avoid warnings for Neo4j 5 id() deprecation ResultProcessor.pm doesn't actually use warnings, so `no warnings` is currently unnecessary, but I'm still adding it in this change. While doing so, I also fix some typos that had gone unnoticed because ResultProcessor.pm didn't use strict, either. Closes #34. --- lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm | 6 +- lib/REST/Neo4p/Agent/Neo4j/ResultProcessor.pm | 58 +++++++++++-------- lib/REST/Neo4p/Entity.pm | 5 +- lib/REST/Neo4p/Node.pm | 2 +- lib/REST/Neo4p/Path.pm | 4 +- lib/REST/Neo4p/Relationship.pm | 6 +- 6 files changed, 47 insertions(+), 34 deletions(-) diff --git a/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm b/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm index 0697f57..8873847 100644 --- a/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm +++ b/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm @@ -858,7 +858,8 @@ sub post_index { for ($ent) { /^node$/ && do { $result = $self->run_in_session("create (n) $set_clause return n"); - $content->{id} = 0+$result->fetch->get(0)->id; + my $node = $result->fetch->get(0); + $content->{id} = do { no warnings 'deprecated'; 0 + $node->id }; if ($self->is_version_4) { my $hkey = encode_base64url($content->{key},''); my $xi_prop = "_xi_$hkey"; @@ -885,7 +886,8 @@ sub post_index { $content->{start} = 0+$start; $content->{end} = 0+$end; $result = $self->run_in_session("match (s), (t) where id(s)=\$start and id(t)=\$end create (s)-[n:$type]->(t) $set_clause return n", $content); - $content->{id} = 0+$result->fetch->get(0)->id; + my $relationship = $result->fetch->get(0); + $content->{id} = do { no warnings 'deprecated'; 0 + $relationship->id }; if ($self->is_version_4) { my $hkey = encode_base64url($content->{key},''); my $xi_prop = "_xi_$hkey"; diff --git a/lib/REST/Neo4p/Agent/Neo4j/ResultProcessor.pm b/lib/REST/Neo4p/Agent/Neo4j/ResultProcessor.pm index e2051e6..9046a1e 100644 --- a/lib/REST/Neo4p/Agent/Neo4j/ResultProcessor.pm +++ b/lib/REST/Neo4p/Agent/Neo4j/ResultProcessor.pm @@ -2,6 +2,7 @@ package REST::Neo4p::Agent::Neo4j::Driver; use REST::Neo4p::Exceptions; +use strict; our %result_processors; @@ -18,8 +19,9 @@ $result_processors{get_node} = sub { my @r = $_->list; REST::Neo4p::NotFoundException->throw unless @r; $r = $r[0]->get(0); - return { metadata => { id => $r->id, labels => [$r->labels] }, - self => 'node/'.$r->id, + my $id = do { no warnings 'deprecated'; $r->id }; + return { metadata => { id => $id, labels => [$r->labels] }, + self => 'node/'.$id, data => $r->properties }; } else { @@ -33,12 +35,13 @@ $result_processors{get_node} = sub { my $ret = []; while (my $rec = $_->fetch) { $r = $rec->get(0); + my $id = do { no warnings 'deprecated'; $r->id }; push @$ret, { - metadata => { id => $r->id, type => $r->type }, - self => 'relationship/'.$r->id, + metadata => { id => $id, type => $r->type }, + self => 'relationship/'.$id, data => $r->properties, - start => 'node/'.$r->start_id, - end => 'node/'.$r->end_id, + start => 'node/' . do { no warnings 'deprecated'; $r->start_id }, + end => 'node/' . do { no warnings 'deprecated'; $r->end_id }, type => $r->type } } @@ -58,9 +61,10 @@ $result_processors{post_node} = sub { my @r = $_->list; REST::Neo4p::NotFoundException->throw unless @r; $r = $r[0]->get(0); + my $id = do { no warnings 'deprecated'; $r->id }; return { - metadata => { id => $r->id, labels => [] }, - self => 'node/'.$r->id, + metadata => { id => $id, labels => [] }, + self => 'node/'.$id, data => {} }; } @@ -73,12 +77,13 @@ $result_processors{post_node} = sub { my @r = $_->list; REST::Neo4p::NotFoundException->throw unless @r; $r = $r[0]->get(0); + my $id = do { no warnings 'deprecated'; $r->id }; return { - metadata => { id => $r-id, type => $r->type }, - self => 'relationship/'.$r->id, + metadata => { id => $id, type => $r->type }, + self => 'relationship/'.$id, data => $r->properties, - start => 'node/'.$r->start_id, - end => 'node/'.$r->end_id, + start => 'node/' . do { no warnings 'deprecated'; $r->start_id }, + end => 'node/' . do { no warnings 'deprecated'; $r->end_id }, type => $r->type }; }; @@ -95,12 +100,13 @@ $result_processors{get_relationship} = sub { my @r = $_->list; REST::Neo4p::NotFoundException->throw() unless @r; my $r = $r[0]->get(0); + my $id = do { no warnings 'deprecated'; $r->id }; return { - metadata => { id => $r->id, type => $r->type }, - self => 'relationship/'.$r->id, + metadata => { id => $id, type => $r->type }, + self => 'relationship/'.$id, data => $r->properties, - start => 'node/'.$r->start_id, - end => 'node/'.$r->end_id, + start => 'node/' . do { no warnings 'deprecated'; $r->start_id }, + end => 'node/' . do { no warnings 'deprecated'; $r->end_id }, type => $r->type }; }; @@ -109,7 +115,7 @@ $result_processors{get_relationship} = sub { ($other[0] eq 'properties') && do { my @r = $_->list; REST::Neo4p::NotFoundException->throw unless @r; - $r = $r[0]->get(0); + my $r = $r[0]->get(0); return $r; }; return; @@ -132,8 +138,9 @@ $result_processors{get_label} = sub { my $ret = []; while (my $rec = $_->fetch) { my $r = $rec->get(0); - push @$ret, { metadata => { id => $r->id, labels => [$r->labels] }, - self => 'node/'.$r->id, + my $id = do { no warnings 'deprecated'; $r->id }; + push @$ret, { metadata => { id => $id, labels => [$r->labels] }, + self => 'node/'.$id, data => $r->properties }; } return $ret; @@ -159,8 +166,9 @@ $result_processors{get_index} = sub { while (my $rec = $_->fetch) { my $r = $rec->get(0); my @labels = (ref($r) =~ /Node/ ? (labels => [$r->labels]) : ()); - push @$ret, { metadata => { id => $r->id, @labels }, - self => 'node/'.$r->id, + my $id = do { no warnings 'deprecated'; $r->id }; + push @$ret, { metadata => { id => $id, @labels }, + self => 'node/'.$id, data => $r->properties }; } REST::Neo4p::NotFoundException->throw unless @$ret; @@ -181,14 +189,14 @@ $result_processors{post_index} = sub { else { my $n = $_->fetch->get(0); return unless ref $n; - my $id = $n->id; + my $id = do { no warnings 'deprecated'; $n->id }; return { metadata => { id => $id }, self => "$ent/$id", ($n->properties ? (data => $n->properties) : ()), - ($n->can(start_id) ? ( - start_id => $n->start_id, - end_id => $n->end_id, + ($n->can('start_id') ? ( + start_id => do { no warnings 'deprecated'; $n->start_id }, + end_id => do { no warnings 'deprecated'; $n->end_id }, type => $n->type ) : ()), indexed => "index/$ent/$idx/$$content{key}/$$content{value}/$id" diff --git a/lib/REST/Neo4p/Entity.pm b/lib/REST/Neo4p/Entity.pm index 737438f..6b6f29d 100644 --- a/lib/REST/Neo4p/Entity.pm +++ b/lib/REST/Neo4p/Entity.pm @@ -83,6 +83,7 @@ sub new_from_json_response { ($obj) = $self_url =~ /([a-z0-9_]+)\/?$/i; } else { # Driver + no warnings 'deprecated'; # id() in Neo4j 5 $obj = $decoded_resp->id; $self_url = "$entity_type/$obj"; } @@ -97,8 +98,8 @@ sub new_from_json_response { } else { # Driver if ($decoded_resp->can('start_id')) { - $start_id = $decoded_resp->start_id; - $end_id = $decoded_resp->end_id; + $start_id = do { no warnings 'deprecated'; $decoded_resp->start_id }; + $end_id = do { no warnings 'deprecated'; $decoded_resp->end_id }; $type = $decoded_resp->type; } } diff --git a/lib/REST/Neo4p/Node.pm b/lib/REST/Neo4p/Node.pm index 180b5da..41b161b 100644 --- a/lib/REST/Neo4p/Node.pm +++ b/lib/REST/Neo4p/Node.pm @@ -236,7 +236,7 @@ sub simple_from_json_response { last; }; $_->isa('Neo4j::Types::Node') && do { # via Neo4j::Driver - $ret->{_node} = $decoded_resp->id; + $ret->{_node} = do { no warnings 'deprecated'; $decoded_resp->id }; $ret->{$_} = $decoded_resp->properties->{$_} for keys %{$decoded_resp->properties}; last; }; diff --git a/lib/REST/Neo4p/Path.pm b/lib/REST/Neo4p/Path.pm index 258f55f..30b82a0 100644 --- a/lib/REST/Neo4p/Path.pm +++ b/lib/REST/Neo4p/Path.pm @@ -69,7 +69,8 @@ sub new_from_driver_obj { my $r = shift @relns; my ($node, $relationship); eval { - $node = REST::Neo4p::Node->_entity_by_id($n->id); + my $id = do { no warnings 'deprecated'; $n->id }; + $node = REST::Neo4p::Node->_entity_by_id($id); }; if (my $e = REST::Neo4p::Exception->caught()) { # TODO : handle different classes @@ -80,6 +81,7 @@ sub new_from_driver_obj { } push @{$obj->{_nodes}}, $node; eval { + no warnings 'deprecated'; # id() in Neo4j 5 $relationship = REST::Neo4p::Relationship->_entity_by_id($r->id) if defined $r; }; if (my $e = REST::Neo4p::Exception->caught()) { diff --git a/lib/REST/Neo4p/Relationship.pm b/lib/REST/Neo4p/Relationship.pm index a2cdc3e..23f19de 100644 --- a/lib/REST/Neo4p/Relationship.pm +++ b/lib/REST/Neo4p/Relationship.pm @@ -63,11 +63,11 @@ sub simple_from_json_response { last; }; $_->isa('Neo4j::Types::Relationship') && do { # via Neo4j::Driver - $ret->{_relationship} = $decoded_resp->id; + $ret->{_relationship} = do { no warnings 'deprecated'; $decoded_resp->id }; $ret->{_type} = $decoded_resp->type; $ret->{$_} = $decoded_resp->properties->{$_} for keys %{$decoded_resp->properties}; - $ret->{_start} = $decoded_resp->start_id; - $ret->{_end} = $decoded_resp->end_id; + $ret->{_start} = do { no warnings 'deprecated'; $decoded_resp->start_id }; + $ret->{_end} = do { no warnings 'deprecated'; $decoded_resp->end_id }; last; }; do { From fd916b189088deb1138a2f07c7a9220524c57e47 Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Sun, 17 Nov 2024 08:54:46 +0100 Subject: [PATCH 5/8] Skip tests for indices failing on Neo4j 5 Neo4j 5 removed a number of deprecated procedures used by Neo4p to implement indices. As a result, indices don't work with Neo4p 0.4003 on Neo4j 5. Simply trying to replace the deprecated calls in DriverActions.pm with the new syntax seemed to work in Neo4j 4, but not Neo4 5. The issue is possibly related to the way Neo4p uses properties on the index. There might be changes that I missed in Neo4j 5 in addition to the new syntax. But since Neo4j 5 currently isn't officially supported by Neo4p, it might not matter much. This change for now simply skips the failing tests. Note that Neo4j < 5.17 doesn't allow query parameters in the new index syntax. --- lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm | 20 ++++++++++++++++++++ lib/REST/Neo4p/Index.md | 2 ++ lib/REST/Neo4p/Index.pm | 2 ++ t/0041_idx_create_unique.t | 12 +++++++----- t/004_idx.t | 10 ++++++---- t/005_db.t | 12 +++++++----- t/007_accessors.t | 1 + t/011_neo4p_synopsis.t | 12 ++++++------ t/030_idx_escape.t | 15 +++++++++------ t/050_v2_schema.t | 18 ++++++++---------- t/099_error.t | 7 +++++-- t/lib/Neo4p/Connect.pm | 14 +++++++++++++- t/rt_80150.t | 12 +++++++----- t/rt_91748.t | 10 ++++++---- 14 files changed, 99 insertions(+), 48 deletions(-) diff --git a/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm b/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm index 8873847..21ba75e 100644 --- a/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm +++ b/lib/REST/Neo4p/Agent/Neo4j/DriverActions.pm @@ -616,6 +616,8 @@ sub get_index { $result = $self->is_version_4 ? $self->run_in_session('call db.indexes() yield name, type, entityType where type = "FULLTEXT" and entityType = $ent return entityType, name, type',{ent => uc $ent}) : $self->run_in_session('call db.index.explicit.list()'); + # Neo4j 4.3+ syntax: + # $self->run_in_session('show indexes yield name, type, entityType where type = "FULLTEXT" and entityType = $ent return entityType, name, type',{ent => uc $ent}) : } else { # find things @@ -697,6 +699,8 @@ sub delete_index { } else { $result = $tx->run("match ()-[r]->() where exists(r.__${idx}__keys) return distinct r.__${idx}__keys"); + # Neo4j 4.3+ syntax: + # $result = $tx->run("match ()-[r]->() where r.`__${idx}__keys` is not null return distinct r.`__${idx}__keys`"); } my (%k,%remove); for my $k ($result->list()) { @@ -712,9 +716,13 @@ sub delete_index { else { #relationship $tx->run("match ()-[r]->() where exists(r.__${idx}__keys) set r += \$rm remove r.__${idx}__keys", { rm => \%remove }); + # Neo4j 4.3+ syntax: + # $tx->run("match ()-[r]->() where r.`__${idx}__keys` is not null set r += \$rm remove r.`__${idx}__keys`", } $tx->commit; $result = $self->run_in_session('call db.index.fulltext.drop($idx)', {idx => $idx}); + # Neo4j 4.3+ syntax: + # $result = $self->run_in_session(sprintf 'DROP INDEX `%s`', $idx); } else { $result = $self->run_in_session('call db.index.explicit.drop($idx)',{idx => $idx}); @@ -785,6 +793,14 @@ sub post_index { token => $content->{type} // "__$$content{name}__index", prop => "__$$content{name}__keys" }); + # Neo4j 4.3+ syntax: + # my $token = $content->{type} // "__$$content{name}__index"; + # my $ent_pattern = ($ent eq 'node') ? "(x:`$token`)" : "()-[x:`$token`]-()"; + # $result = $self->run_in_session( + # sprintf "CREATE FULLTEXT INDEX `%s` FOR %s ON EACH [x.`%s`] OPTIONS {indexConfig: {`fulltext.analyzer`:'whitespace'}}", + # $content->{name}, $ent_pattern, "__$$content{name}__keys" + # )->consume; + # $result = $self->run_in_session("RETURN 1"); } else { my $for = ($ent eq 'node') ? 'forNodes' : 'forRelationships'; @@ -947,6 +963,8 @@ sub get_schema_constraint { my @constraints; my $result; $result = $self->run_in_session('call db.constraints()'); + # Neo4j 4.3+ syntax: + # $result = $self->run_in_session($self->is_version_4 ? 'SHOW CONSTRAINTS' : 'call db.constraints()'); if ($result) { while (my $rec = $result->fetch) { my ($node_label,$reln_type,$x_prop, $u_prop) = @@ -1062,6 +1080,8 @@ sub get_schema_index { my $q; if ($maj > 3) { $q = 'call db.indexes() yield labelsOrTypes as labels, properties where $lbl in labels return { label:$lbl, property_keys:properties }'; + # Neo4j 4.3+ syntax: + # $q = 'show indexes yield labelsOrTypes as labels, properties where $lbl in labels return { label:$lbl, property_keys:properties }'; } elsif ($maj==3 && $min>=5) { # patch $q = 'call db.indexes() yield tokenNames as labels, properties where $lbl in labels return { label:$lbl, property_keys:properties }'; diff --git a/lib/REST/Neo4p/Index.md b/lib/REST/Neo4p/Index.md index 772f6cd..46ab572 100644 --- a/lib/REST/Neo4p/Index.md +++ b/lib/REST/Neo4p/Index.md @@ -37,6 +37,8 @@ functionality, the agent based on [Neo4j::Driver](https://metacpan.org/pod/Neo4j indexes under the hood to emulate explicit indexes. This agent is used automatically with Neo4j version 4.0 servers. +Note that this index emulation currently doesn't work with Neo4j 5. + # METHODS - new() diff --git a/lib/REST/Neo4p/Index.pm b/lib/REST/Neo4p/Index.pm index e672ff6..519981b 100644 --- a/lib/REST/Neo4p/Index.pm +++ b/lib/REST/Neo4p/Index.pm @@ -359,6 +359,8 @@ functionality, the agent based on L uses fulltext indexes under the hood to emulate explicit indexes. This agent is used automatically with Neo4j version 4.0 servers. +Note that this index emulation currently doesn't work with S + =head1 METHODS =over diff --git a/t/0041_idx_create_unique.t b/t/0041_idx_create_unique.t index 8ae7f23..35ef428 100644 --- a/t/0041_idx_create_unique.t +++ b/t/0041_idx_create_unique.t @@ -1,5 +1,5 @@ #$Id$ -use Test::More tests => 24; +use Test::More; use Module::Build; use lib '../lib'; use lib qw'lib t/lib'; @@ -17,15 +17,17 @@ eval { $pass = $build->notes('pass'); }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 23; -use_ok('REST::Neo4p'); +use REST::Neo4p; my $not_connected = connect($TEST_SERVER, $user, $pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 23; + +{ my @cleanup; if ( my $i = REST::Neo4p->get_index_by_name('nidx555','node') ) { $i->remove } if ( my $i = REST::Neo4p->get_index_by_name('ridx555','relationship') ) { $i->remove } diff --git a/t/004_idx.t b/t/004_idx.t index 7a79b37..75de2b9 100644 --- a/t/004_idx.t +++ b/t/004_idx.t @@ -14,15 +14,17 @@ eval { $pass = $build->notes('pass'); }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 67; -use_ok('REST::Neo4p'); +use REST::Neo4p; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 67; + +{ my @node_defs = ( { name => 'A', type => 'purine' }, diff --git a/t/005_db.t b/t/005_db.t index e806ee1..59a337a 100644 --- a/t/005_db.t +++ b/t/005_db.t @@ -1,6 +1,6 @@ #-*-perl-*- #$Id$ -use Test::More tests => 33; +use Test::More; use Test::Exception; use Module::Build; use lib '../lib'; @@ -20,15 +20,17 @@ eval { $pass = $build->notes('pass'); }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 32; -use_ok('REST::Neo4p'); +use REST::Neo4p; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 32; + +{ ok my $n1 = REST::Neo4p::Node->new(), 'node 1'; # push @cleanup, $n1 if $n1; ok my $n2 = REST::Neo4p::Node->new(), 'node 2'; diff --git a/t/007_accessors.t b/t/007_accessors.t index 14935aa..4370cca 100644 --- a/t/007_accessors.t +++ b/t/007_accessors.t @@ -51,6 +51,7 @@ SKIP : { lives_and { ok $n3->set_blue(5) } 'blue setter called'; lives_and { is $n3->blue, 5 } 'blue setter works'; my $idx; + if (neo4j_index_unavailable()) { pass; pass; diag neo4j_index_unavailable(); last SKIP; } lives_ok {$idx = REST::Neo4p::Index->new('relationship','heydude', {rtype => 'dude'})} 'index should be created np'; push @cleanup, $idx if $idx; diff --git a/t/011_neo4p_synopsis.t b/t/011_neo4p_synopsis.t index fa78f1c..5553d31 100644 --- a/t/011_neo4p_synopsis.t +++ b/t/011_neo4p_synopsis.t @@ -1,6 +1,6 @@ #-*-perl-*- #$Id$ -use Test::More qw(no_plan); +use Test::More 0.88; use Test::Exception; use Module::Build; use lib '../lib'; @@ -11,7 +11,7 @@ use strict; use warnings; no warnings qw(once); my @cleanup; -use_ok('REST::Neo4p'); +use REST::Neo4p; my $build; my ($user,$pass) = @ENV{qw/REST_NEO4P_TEST_USER REST_NEO4P_TEST_PASS/}; @@ -24,15 +24,15 @@ eval { }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 13; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 13 + 4; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; - +{ ok my $i = REST::Neo4p::Index->new('node', 'my_node_index'); push @cleanup, $i if $i; my $f; diff --git a/t/030_idx_escape.t b/t/030_idx_escape.t index 7143f43..c967a83 100644 --- a/t/030_idx_escape.t +++ b/t/030_idx_escape.t @@ -1,7 +1,7 @@ #-*- perl -*- #$Id$ -use Test::More qw(no_plan); +use Test::More 0.88; use Module::Build; use lib '../lib'; use lib 'lib'; @@ -21,15 +21,16 @@ eval { }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 1; - -use_ok('REST::Neo4p'); +use REST::Neo4p; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 4 + 5; + +{ my @node_defs = ( { name => 'A', type => 'purine' }, @@ -53,3 +54,5 @@ SKIP : { ok ($_->remove, 'entity removed') for reverse @cleanup; } } + +done_testing; diff --git a/t/050_v2_schema.t b/t/050_v2_schema.t index 343a130..521adfd 100644 --- a/t/050_v2_schema.t +++ b/t/050_v2_schema.t @@ -1,5 +1,5 @@ #$Id# -use Test::More tests => 29; +use Test::More; use Test::Exception; use Module::Build; use lib '../lib'; @@ -23,17 +23,17 @@ eval { $pass = $build->notes('pass'); }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 29; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; - my $version = REST::Neo4p->neo4j_version; - my $VERSION_OK = REST::Neo4p->_check_version(2,0,1); - SKIP : { - skip "Server version $version < 2.0.1", $num_live_tests unless $VERSION_OK; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan skip_all => sprintf 'Server version %s < 2.0.1', scalar REST::Neo4p->neo4j_version + unless REST::Neo4p->_check_version(2,0,1); +plan tests => 29; + +{ ok my $schema = REST::Neo4p::Schema->new, 'new Schema object'; isa_ok $schema, 'REST::Neo4p::Schema'; is $schema->_handle, REST::Neo4p->handle, 'handle correct'; @@ -78,8 +78,6 @@ SKIP : { ok $n2->set_property({name=>"Fred"}), 'constraint lifted, can set label'; is $n2->get_property('name'), 'Fred', 'property now set'; 1; - } - } END { diff --git a/t/099_error.t b/t/099_error.t index f0eac01..892061a 100644 --- a/t/099_error.t +++ b/t/099_error.t @@ -1,5 +1,5 @@ #$Id$ -use Test::More qw(no_plan); +use Test::More 0.88; use Test::Exception; use Module::Build; use lib '../lib'; @@ -21,7 +21,7 @@ eval { }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 1; +my $num_live_tests = 18; throws_ok { REST::Neo4p->get_indexes('relationship') } 'REST::Neo4p::CommException', 'not connected ok'; like $@->message, qr/not connected/i, 'not connected ok (2)'; @@ -57,6 +57,7 @@ SKIP : { lives_ok { $q->err } 'LocalException->code works'; $q->{_error} = REST::Neo4p::TxException->new(); lives_ok { $q->err } 'TxException->code works'; + if (neo4j_index_unavailable()) { diag neo4j_index_unavailable(); last SKIP; } ok my $i = REST::Neo4p::Index->new('node','zzyxx'), 'create index'; throws_ok { $i->get_property('foo') } 'REST::Neo4p::NotSuppException', 'not supported ok'; throws_ok { $i->set_property(foo => 'bar') } 'REST::Neo4p::NotSuppException', 'not supported ok (2)'; @@ -67,3 +68,5 @@ SKIP : { ok $n1->remove, 'remove node'; ok $i->remove, 'remove index'; } + +done_testing; diff --git a/t/lib/Neo4p/Connect.pm b/t/lib/Neo4p/Connect.pm index e292965..6502eb3 100644 --- a/t/lib/Neo4p/Connect.pm +++ b/t/lib/Neo4p/Connect.pm @@ -4,7 +4,7 @@ use REST::Neo4p; use strict; use warnings; -our @EXPORT=qw/connect/; +our @EXPORT=qw/connect neo4j_index_unavailable/; sub connect { my ($TEST_SERVER,$user,$pass) = @_; @@ -25,3 +25,15 @@ sub connect { return $e; } } + +sub neo4j_index_unavailable { + return unless REST::Neo4p->connected; + return 'Neo4j 5 index syntax unimplemented' if REST::Neo4p->neo4j_version =~ /^5\./; + return if REST::Neo4p->neo4j_version =~ /^[014]\./; + return if REST::Neo4p->neo4j_version =~ /^3\.5\./; + # For Neo4j 2 and 3 (before 3.5), only native indexes are available. + return unless REST::Neo4p->agent->isa('REST::Neo4p::Agent::Neo4j::Driver'); + return 'Neo4j::Driver uses fulltext index for emulation, which is only available starting from Neo4j 3.5'; +} + +1; diff --git a/t/rt_80150.t b/t/rt_80150.t index 2b2e548..2f9b0f1 100644 --- a/t/rt_80150.t +++ b/t/rt_80150.t @@ -1,6 +1,6 @@ #-*-perl-*- #$Id$ -use Test::More tests => 4; +use Test::More; use Test::Exception; use Module::Build; use lib qw|../lib lib|; @@ -19,15 +19,17 @@ eval { $pass = $build->notes('pass'); }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 3; -use_ok('REST::Neo4p'); +use REST::Neo4p; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 3; + +{ ok my $idx = REST::Neo4p::Index->new('node', 'medicago_truncatula_3_5v5'); is $idx->name('medicago_truncatula_3_5v5'), 'medicago_truncatula_3_5v5', 'index name is correct'; diff --git a/t/rt_91748.t b/t/rt_91748.t index c4f68b4..d0f06c8 100644 --- a/t/rt_91748.t +++ b/t/rt_91748.t @@ -1,5 +1,5 @@ #$Id$ -use Test::More tests => 2; +use Test::More; use Test::Exception; use Module::Build; use lib qw|../lib lib|; @@ -20,13 +20,15 @@ eval { $pass = $build->notes('pass'); }; my $TEST_SERVER = $build ? $build->notes('test_server') : $ENV{REST_NEO4P_TEST_SERVER} // 'http://127.0.0.1:7474'; -my $num_live_tests = 2; my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -SKIP : { - skip 'no local connection to neo4j', $num_live_tests if $not_connected; +plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); +plan skip_all => 'no local connection to neo4j' if $not_connected; +plan tests => 2; + +{ $index = REST::Neo4p::Index->new('node', $test_index); lives_ok { $n = $index->create_unique(bar => "0", {bar => "0"}) From 3b745194f433b8dfc9d3a4efa35623b491abd3c2 Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Sun, 17 Nov 2024 08:45:52 +0100 Subject: [PATCH 6/8] Fix Cypher param syntax in test suite on Neo4j 2 Back when Neo4p was updated for Neo4j 4, some tests were rewritten to use the $param syntax, which broke those tests on old Neo4j versions. This change reverts the syntax in affected tests back to {param} and adds the Neo4j::Driver option cypher_params v2 to keep things working on Neo4j 4+. The cypher_params option replaced the experimental cypher_filter option in 2021. https://metacpan.org/release/AJNN/Neo4j-Driver-0.52/view/lib/Neo4j/Driver/Deprecations.pod#Parameter-syntax-conversion-using-cypher_filter --- Build.PL | 2 +- t/011_neo4p_synopsis.t | 4 ++-- t/041_v2_txns.t | 4 +--- t/id_roundtrip.t | 8 ++++---- t/json_utf8.t | 8 ++++---- t/lib/Neo4p/Connect.pm | 13 +++++++++++++ t/lib/Neo4p/Test.pm | 4 ++-- t/rt_91640.t | 2 +- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/Build.PL b/Build.PL index df1adc9..267b845 100644 --- a/Build.PL +++ b/Build.PL @@ -96,6 +96,7 @@ my $build = $class->new 'Mock::Quick' => 0, 'List::MoreUtils' => 0, 'Mojo::Exception' => 0, + 'Neo4j::Driver' => '0.26', # cypher_params v2 experimental => 0, 'IPC::Run' => 0, 'IO::Pty' => 0, @@ -105,7 +106,6 @@ my $build = $class->new 'Test::Pod' => 1.0, 'Mojo::UserAgent' => 0, 'HTTP::Thin' => 0, - 'Neo4j::Driver' => '0.19', }, meta_merge => { resources => { diff --git a/t/011_neo4p_synopsis.t b/t/011_neo4p_synopsis.t index 5553d31..bc9e931 100644 --- a/t/011_neo4p_synopsis.t +++ b/t/011_neo4p_synopsis.t @@ -6,7 +6,7 @@ use Module::Build; use lib '../lib'; use lib 'lib'; use lib 't/lib'; -use Neo4p::Connect; +use Neo4p::Connect ':cypher_params_v2'; use strict; use warnings; no warnings qw(once); @@ -49,7 +49,7 @@ plan tests => 13 + 4; is $my_reln->end_node->get_property('name'), 'Donkey Hoty', 'got Donkey Hoty'; push @cleanup, $my_reln if $my_reln; ok my $query = REST::Neo4p::Query->new("MATCH p = (n)-[]->() - WHERE id(n) = \$id + WHERE id(n) = {id} RETURN p", {id => $my_node->id}); ok $query->execute; my $path = $query->fetch->[0]; diff --git a/t/041_v2_txns.t b/t/041_v2_txns.t index 1828d82..7fe2d06 100644 --- a/t/041_v2_txns.t +++ b/t/041_v2_txns.t @@ -6,7 +6,7 @@ use lib 'lib'; use lib 't/lib'; use REST::Neo4p; use Neo4p::Test; -use Neo4p::Connect; +use Neo4p::Connect ':cypher_params_v2'; use strict; use warnings; no warnings qw(once); @@ -35,8 +35,6 @@ my $neo4p = 'REST::Neo4p'; my ($n, $m); SKIP : { skip 'no local connection to neo4j', $num_live_tests if $not_connected; - REST::Neo4p->create_and_set_handle(cypher_filter => 'params'); - connect($TEST_SERVER, $user, $pass); ok my $t = Neo4p::Test->new, 'test graph object'; ok $t->create_sample, 'create sample graph'; diff --git a/t/id_roundtrip.t b/t/id_roundtrip.t index 19adb6a..2aa06a8 100644 --- a/t/id_roundtrip.t +++ b/t/id_roundtrip.t @@ -4,7 +4,7 @@ use Test::Exception; use Module::Build; use lib qw|../lib lib|; use lib 't/lib'; -use Neo4p::Connect; +use Neo4p::Connect ':cypher_params_v2'; use strict; use warnings; my @cleanup; @@ -28,7 +28,7 @@ SKIP : { my $n1 = REST::Neo4p::Node->new(); ok $n1, 'new node' and push @cleanup, $n1; - my $q = REST::Neo4p::Query->new('MATCH (n) WHERE id(n) = $id RETURN n'); + my $q = REST::Neo4p::Query->new('MATCH (n) WHERE id(n) = {id} RETURN n'); $q->{RaiseError} = 1; my $row; @@ -40,7 +40,7 @@ SKIP : { # # Example code: # - # my $q = REST::Neo4p::Query->new('MATCH (n) WHERE id(n) = $id RETURN n'); + # my $q = REST::Neo4p::Query->new('MATCH (n) WHERE id(n) = {id} RETURN n'); # my $id = REST::Neo4p::Node->new()->id(); # $q->execute( id => $id ); # $q->fetch; @@ -72,7 +72,7 @@ SKIP : { ok $n2, '2nd node' and push @cleanup, $n2; my $r = REST::Neo4p::Relationship->new( $n1 => $n2, 'TEST' ); ok $r, 'new rel' and push @cleanup, $r; - $q = REST::Neo4p::Query->new('MATCH ()-[r]->() WHERE id(r) = $id RETURN r'); + $q = REST::Neo4p::Query->new('MATCH ()-[r]->() WHERE id(r) = {id} RETURN r'); $q->{RaiseError} = 1; eval { diff --git a/t/json_utf8.t b/t/json_utf8.t index cd32aa0..440517a 100644 --- a/t/json_utf8.t +++ b/t/json_utf8.t @@ -3,7 +3,7 @@ use Test::More tests => 38; use Module::Build; use lib qw|../lib lib|; use lib 't/lib'; -use Neo4p::Connect; +use Neo4p::Connect ':cypher_params_v2'; use strict; use warnings; my @cleanup; @@ -58,7 +58,7 @@ SKIP : { # ->get_properties worked as well # ->as_simple worked as well (probably via the entity cache) - my $q2 = REST::Neo4p::Query->new("MATCH (n) WHERE id(n) = \$id RETURN n", $id_param); + my $q2 = REST::Neo4p::Query->new("MATCH (n) WHERE id(n) = {id} RETURN n", $id_param); $q2->{ResponseAsObjects} = 0; $q2->execute; eval { $row = $q2->fetch }; @@ -70,7 +70,7 @@ SKIP : { # Node::simple_from_json_response $q2->finish; - my $q3 = REST::Neo4p::Query->new("MATCH (n) WHERE id(n) = \$id RETURN n." . (join ", n.", @keys), $id_param); + my $q3 = REST::Neo4p::Query->new("MATCH (n) WHERE id(n) = {id} RETURN n." . (join ", n.", @keys), $id_param); $q3->execute; eval { $row = $q3->fetch }; ok $row, 'fetch node properties'; @@ -86,7 +86,7 @@ SKIP : { ok $r1, 'create rel' and push @cleanup, $r1; $id_param = { id => 0 + $r1->id }; - my $q5 = REST::Neo4p::Query->new("MATCH ()-[r]-() WHERE id(r) = \$id RETURN r." . (join ", r.", @keys), $id_param); + my $q5 = REST::Neo4p::Query->new("MATCH ()-[r]-() WHERE id(r) = {id} RETURN r." . (join ", r.", @keys), $id_param); $q5->execute; eval { $row = $q5->fetch }; ok $row, 'fetch rel properties'; diff --git a/t/lib/Neo4p/Connect.pm b/t/lib/Neo4p/Connect.pm index 6502eb3..c52233e 100644 --- a/t/lib/Neo4p/Connect.pm +++ b/t/lib/Neo4p/Connect.pm @@ -6,10 +6,23 @@ use warnings; our @EXPORT=qw/connect neo4j_index_unavailable/; +our $cypher_params_v2; + +sub import { + $cypher_params_v2 = splice @_, $_, 1 for grep $_[$_] eq ':cypher_params_v2', reverse 0 .. $#_; + goto &Exporter::import; +} + sub connect { my ($TEST_SERVER,$user,$pass) = @_; eval { REST::Neo4p->connect($TEST_SERVER,$user,$pass); + + # Allow tests running on Neo4j 4+ to use the {} param syntax. + if ($cypher_params_v2 && REST::Neo4p->agent->isa('REST::Neo4p::Agent::Neo4j::Driver')) { + REST::Neo4p->create_and_set_handle(cypher_params => v2); + REST::Neo4p->connect($TEST_SERVER,$user,$pass); + } }; if ( my $e = REST::Neo4p::CommException->caught() ) { if ($e->message =~ /certificate verify failed/i) { diff --git a/t/lib/Neo4p/Test.pm b/t/lib/Neo4p/Test.pm index 52a1ce0..8501adb 100644 --- a/t/lib/Neo4p/Test.pm +++ b/t/lib/Neo4p/Test.pm @@ -69,7 +69,7 @@ sub find_sample { my ($k,$v) = @_; $v+=0 if looks_like_number $v; my $lbl = $self->lbl; - my $q = REST::Neo4p::Query->new("MATCH (n:$lbl) where n.$k = \$value return n"); + my $q = REST::Neo4p::Query->new("MATCH (n:$lbl) where n.$k = {value} return n"); $q->execute({value => $v}); my @ret; while (my $r = $q->fetch) { @@ -82,7 +82,7 @@ sub delete_sample { my $self = shift; die "No connection" unless REST::Neo4p->connected; my $lbl = $self->lbl; - my $q = REST::Neo4p::Query->new("match (n:$lbl) where n.uuid = \$uuid detach delete n",{uuid => $self->uuid}); + my $q = REST::Neo4p::Query->new("match (n:$lbl) where n.uuid = {uuid} detach delete n",{uuid => $self->uuid}); $q->execute(); return 1; } diff --git a/t/rt_91640.t b/t/rt_91640.t index a9736ff..4baa6ca 100644 --- a/t/rt_91640.t +++ b/t/rt_91640.t @@ -8,7 +8,7 @@ use lib 'lib'; use lib 't/lib'; use REST::Neo4p; use Neo4p::Test; -use Neo4p::Connect; +use Neo4p::Connect ':cypher_params_v2'; use strict; use warnings; no warnings qw(once); From 6b6e6e96ae7d3a25a344b8c4b8504795a1a4c834 Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Fri, 6 Dec 2024 07:44:30 +0100 Subject: [PATCH 7/8] Skip tests with MATCH queries failing on Neo4j 1 Back when Neo4p was updated for Neo4j 4, queries that begin with MATCH were added to some tests. My understanding is that such queries are unsupported on Neo4j 1. This change simply skips the affected tests on Neo4j 1. --- t/006_query.t | 1 + t/011_neo4p_synopsis.t | 1 + t/id_roundtrip.t | 1 + t/json_utf8.t | 1 + t/rt_81128.t | 1 + t/rt_91640.t | 6 +++--- 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/t/006_query.t b/t/006_query.t index ea926e1..385d4da 100644 --- a/t/006_query.t +++ b/t/006_query.t @@ -41,6 +41,7 @@ is_deeply $q->params, { node_id => 1}, 'params accessor'; SKIP : { skip 'no local connection to neo4j', $num_live_tests if $not_connected; + skip 'MATCH query requires Neo4j 2 or later', $num_live_tests unless REST::Neo4p->_check_version(2,0,0,0); ok my $n1 = REST::Neo4p::Node->new({name => 'Fred', role => 'husband'}), 'Fred'; ok my $n2 = REST::Neo4p::Node->new({name => 'Wilma', role => 'wife'}), 'Wilma'; diff --git a/t/011_neo4p_synopsis.t b/t/011_neo4p_synopsis.t index bc9e931..33a1e21 100644 --- a/t/011_neo4p_synopsis.t +++ b/t/011_neo4p_synopsis.t @@ -30,6 +30,7 @@ diag "Test server unavailable (".$not_connected->message.") : tests skipped" if plan skip_all => neo4j_index_unavailable() if neo4j_index_unavailable(); plan skip_all => 'no local connection to neo4j' if $not_connected; +plan skip_all => 'MATCH query requires Neo4j 2 or later' unless REST::Neo4p->_check_version(2,0,0,0); plan tests => 13 + 4; { diff --git a/t/id_roundtrip.t b/t/id_roundtrip.t index 2aa06a8..7de2fd7 100644 --- a/t/id_roundtrip.t +++ b/t/id_roundtrip.t @@ -25,6 +25,7 @@ diag "Test server unavailable (".$not_connected->message.") : tests skipped" if SKIP : { skip 'no local connection to neo4j', 34 if $not_connected; + skip 'MATCH query requires Neo4j 2 or later', 34 unless REST::Neo4p->_check_version(2,0,0,0); my $n1 = REST::Neo4p::Node->new(); ok $n1, 'new node' and push @cleanup, $n1; diff --git a/t/json_utf8.t b/t/json_utf8.t index 440517a..6bbc3b1 100644 --- a/t/json_utf8.t +++ b/t/json_utf8.t @@ -34,6 +34,7 @@ sub to_hex ($) { SKIP : { skip 'no local connection to neo4j', 37 if $not_connected; + skip 'MATCH query requires Neo4j 2 or later', 37 unless REST::Neo4p->_check_version(2,0,0,0); my %props = ( singlebyte => "\N{U+0025}", # '%' PERCENT SIGN = 0x25 diff --git a/t/rt_81128.t b/t/rt_81128.t index 9ebe88c..e2dbc4c 100644 --- a/t/rt_81128.t +++ b/t/rt_81128.t @@ -29,6 +29,7 @@ diag "Test server unavailable (".$not_connected->message.") : tests skipped" if SKIP : { skip 'no local connection to neo4j', $num_live_tests if $not_connected; + skip 'MATCH query requires Neo4j 2 or later', $num_live_tests unless REST::Neo4p->_check_version(2,0,0,0); ok my $n1 = REST::Neo4p::Node->new( {name => 'ricky'} ), 'new node 1'; push @cleanup, $n1 if $n1; diff --git a/t/rt_91640.t b/t/rt_91640.t index 4baa6ca..830ceb2 100644 --- a/t/rt_91640.t +++ b/t/rt_91640.t @@ -29,8 +29,8 @@ my $not_connected = connect($TEST_SERVER,$user,$pass); diag "Test server unavailable (".$not_connected->message.") : tests skipped" if $not_connected; -#SKIP : { -# skip "Neo4j server version >= 2.0.0-M02 required, skipping...", $num_live_tests unless REST::Neo4p->_check_version(2,0,0,2); +SKIP : { + skip "Neo4j server version >= 2.0.0-M02 required, skipping...", $num_live_tests unless REST::Neo4p->_check_version(2,0,0,2); my $neo4p = 'REST::Neo4p'; my ($n, $m, $t); @@ -80,9 +80,9 @@ STMT1 is $$r[0]->get_property('utf8'), 'Сохранить', 'cyrillic property value retrieved correctly'; } -#} END { eval { $t && $t->delete_sample; }; } +} From 75aad27d82aaee8c5c9cb5de208045fcf8a798d3 Mon Sep 17 00:00:00 2001 From: Arne Johannessen Date: Fri, 6 Dec 2024 08:36:01 +0100 Subject: [PATCH 8/8] Skip test failing due to tx endpoint metadata bug REST::Neo4p 0.4003 can't parse the metadata for entities returned from Neo4j < 3.5 via the transaction endpoint (unless the Neo4j::Driver agent is used). This change prevents that bug from breaking the test suite. To reproduce the bug (will trigger a "Can't call method on unblessed reference" error): REST::Neo4p->begin_work; (my $q = REST::Neo4p::Query->new('MATCH (n) RETURN n LIMIT 1'))->execute; print $q->fetch->[0]->id; Issues with the tx response format aren't new. The changelog for version 0.2220 included a fix for it, but the response format was updated in later Neo4j versions, forcing further changes. I believe the current iteration of the bug may have been introduced in https://github.com/majensen/rest-neo4p/commit/2077e4bdbfc62f14482cc692172ec8539a1375b6. --- t/041_v2_txns.t | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/t/041_v2_txns.t b/t/041_v2_txns.t index 7fe2d06..824ccaa 100644 --- a/t/041_v2_txns.t +++ b/t/041_v2_txns.t @@ -31,6 +31,14 @@ diag "Test server unavailable (".$not_connected->message.") : tests skipped" if SKIP : { skip "Neo4j server version >= 2.0.0-M02 required, skipping...", $num_live_tests unless REST::Neo4p->_check_version(2,0,0,2); + skip 'Returning entities via tx endpoint needs either Driver agent or Neo4j 3.5+', $num_live_tests + unless REST::Neo4p->_check_version(3,5,0,0) + || REST::Neo4p->agent->isa('REST::Neo4p::Agent::Neo4j::Driver'); + # There is a known bug in _process_row/_response_entity in REST::Neo4p::Query + # that can prevent correct parsing of metadata for entities returned via the + # transaction endpoint. The metadata format varied between Neo4j versions. + # REST::Neo4p 0.3012-0.3030 worked correctly on some (not all) Neo4j versions. + my $neo4p = 'REST::Neo4p'; my ($n, $m); SKIP : {