Neo4j-Driver/jolt.pl
#! /usr/bin/env perl
use 5.016;
use warnings;
use lib qw(lib);
use Neo4j::Driver;
use Scalar::Util qw(blessed);
use XXX -with => 'Data::Dump';
# use Carp::Always;
# use Devel::Confess;
#use Neo4j::Driver::
# use Neo4j::Driver::Net::HTTP::Tiny;
# use Neo4j::Driver::Net::HTTP::AnyEvent;
# use Neo4j::Driver::Net::HTTP::REST;
#say "Using ", AnyEvent::detect;
print "This is Neo4j::Driver ", ($Neo4j::Driver::VERSION // "DEV"), "\n";
my $d = Neo4j::Driver->new('bolt://127.0.0.1')->basic_auth('neo4j', 'pass');
$d->config(cypher_params=>v2);
# $d->{net_module} = 'Neo4j::Driver::Net::HTTP::AnyEvent';
# $d->{jolt} = '0';
#XXX $d->session(database=>'system')->run('SHOW DEFAULT DATABASE');
my $s = $d->session;
my $protocol = (map {$_ ? "Bolt/$_" : defined $_ ? "Bolt" : "HTTP/1.1"} $s->server->protocol_version)[0];
say $s->server->agent, " ", $protocol, " (T ", ($s->server->{time_diff}//"undef"), ")";
my $r = $s->run('return "Server OK."');
say $r->fetch->get, $r->isa('Neo4j::Driver::Result::Jolt') ? " (Jolt)" : "";
# {
# # local $Neo4j::Driver::Result::Jolt::gather_results = 0;
# say $s->run('return "autocommit 1"')->fetch->get;
# my $t;
# $s->execute_read( sub {
# $t = shift;
# $r = $t->run('UNWIND [7,8,9] AS x RETURN x');
# say $r->keys; # keys() right after run() might be worth adding to the test suite if not already in there
# say join '/', map { $_->get } $r->list;
# my @statements = (
# [ 'RETURN 11' ],
# [ 'RETURN 22' ],
# [ 'RETURN 33' ],
# );
# my @results = $t->_run_multiple(@statements);
# foreach my $result ( @results ) {
# say $result->single->get;
# }
# say 'tx closed unexpectedly' if ! $t->is_open;
# YYY keys $s->{net}{active_tx}->%* if ref $s->{net}{active_tx};
# });
# say $t && $t->is_open ? 'tx open after commit - error!' : 'tx closed after commit ok - Success!';
# ############ problem: tx is marked as closed, but bot actually removed from active_tx list for some reason
# YYY keys $s->{net}{active_tx}->%* if ref $s->{net}{active_tx};
# say $s->run('return "autocommit 2"')->fetch->get;
# }
#
# __END__
sub _looks_like_number {
my $value = shift;
no warnings 'numeric';
return -1 if ref($value);
# if the utf8 flag is on, it almost certainly started as a string
return if utf8::is_utf8($value);
# detect numbers
# string & "" -> ""
# number & "" -> 0 (with warning)
# nan and inf can detect as numbers, so check with * 0
return unless length((my $dummy = "") & $value);
return unless 0 + $value eq $value;
return 1 if $value * 0 == 0;
return -1; # inf/nan
}
no warnings 'experimental::builtin';
$r = $s->run(<<END, t => builtin::true, f => builtin::false);
MATCH p=(n)-[r]->()<-[]-()
RETURN
null as Null,
{t} as BooleanTrue,
{f} as BooleanFalse,
42 as Integer,
0.5 as Float,
log(-1) as FloatNaN,
//9.9^999 as FloatPosInf,
//-9.9^999 as FloatNegInf,
-0.00 as FloatNegZero,
"hello" as String,
date('1984-10-11') as Date,
time('125035.556+0100') as Time,
localtime('12:50:35.556') as LocalTime,
datetime('2015-06-24T12:50:35.556+0000') as DateTime,
//datetime({year: 1987, month: 12, day: 18, hour: 12, timezone: 'America/Los Angeles'}) as DateTimeZoneId,
localdatetime('2015185T19:32:24') as LocalDateTime,
duration('P29WT31M0.001S') as Duration,
//duration.between(date('2015-06-24'), date('1984-10-11')) as DurationBetween,
//duration('P0D') as Duration0,
//duration('P-0.5D') as Duration1,
//duration('PT-0.2S') as Duration2,
//duration('P-0.77Y') as Duration3,
point({ latitude:-72, longitude:2.5 }) as PointGeod2D,
point({ x:3, y:0, z:1 }) as PointCart3D,
//n.test as Bytes,
apoc.util.compress("fooƶ",{compression:"NONE"}) as Bytes,
[0.1, 0.2] as List,
{a:0.1, b:0.2} as Map,
n as Node,
r as Relationship,
p as Path
LIMIT 1
END
# YYY $r;
use Data::Dump;
my @keys = $r->keys;
$r = $r->single;
for my $i ( 0 .. $#keys ) {
my $v = $r->get($i);
my $t = "";
$t = "undef" if ! defined $v;
$t = $v if defined $v && _looks_like_number $v;
$t = "'$v'" if defined $v && ! _looks_like_number $v;
$t = ref($v) if ref($v);
$t .= " (core bool)" if builtin::is_bool($v);
$t .= " \\$v" if ref($v) eq 'JSON::PP::Boolean';
$t .= " " . $v->type if blessed $v && $v->isa('Neo4j::Types::DateTime');
$t .= sprintf " %sM %sD %sS", $v->months, $v->days, $v->seconds + $v->nanoseconds / 1e9 if blessed $v && $v->isa('Neo4j::Types::Duration');
$t .= ' ' . join ' ', $v->srid < 6000 ? 'geod' : 'cart', $v->coordinates if blessed $v && $v->isa('Neo4j::Types::Point');
# YYY $v if blessed $v && $v->isa('Neo4j::Types::DateTime');
# say $v->{T} if blessed $v && $v->isa('Neo4j::Types::Duration');
# YYY $v if blessed $v && $v->isa('Neo4j::Types::Point');
{
no warnings 'deprecated';
$t .= " " . $v->id . " " . join ",", map {":$_"} $v->labels if ref($v) eq 'Neo4j::Driver::Type::Node';
$t .= " " . $v->id . " :" . $v->type if ref($v) eq 'Neo4j::Driver::Type::Relationship';
}
$t .= " (" . scalar(my @a = $v->relationships) . ")" if ref($v) eq 'Neo4j::Driver::Type::Path';
$t .= " " . $v->as_string if blessed $v && $v->isa('Neo4j::Types::ByteArray');
$t .= " " . (keys %$v)[0] if ref($v) eq 'HASH' && 1 == keys %$v;
$t .= " " . $v->{(keys %$v)[0]} if ref($v) eq 'HASH' && 1 == keys %$v;
say sprintf "%2d %-14s %s", $i, $keys[$i], $t;
}
# $r = YYY $r->get(17);
# use Devel::Peek;
# Dump $r;
# die $r;
# my $t = $s->begin_transaction;
# $t->run('RETURN 1');
# $t->rollback;
#
# print "Default database: ";
# say $d->session(database=>'system')->run('SHOW DEFAULT DATABASE')->single->get('name');
# say $d->session->run('return "All done."')->single->get;
#YYY $s->run('MATCH (n) WHERE id(n) = 528 RETURN n.test')->single->get;
# Byte array in JSON:
# { meta => [undef], rest => [[70, 111, 111]], row => [[70, 111, 111]] }
# at lib/Neo4j/Driver/Result/JSON.pm line 139
# Byte array in Jolt:
# { "#" => "466F6F" }
# Sparse format seems to affect:
# Boolean, Integer, String, Array
#$s->run([['UNWIND [7,8,9] AS x RETURN x'],['RETURN [4,5],6']]);
#YYY $r=$s->run('EXPLAIN MATCH (n), (m) RETURN n, m');
__END__
# ##### TESTING fetch_event
#
# use AnyEvent::Strict; # AE_STRICT=1
#
my $e = 200;
my $x = $s->run('UNWIND range({e}, 2, -1) AS x RETURN x', e=> $e);
# print "starting fetch "; system "date";
while (my $y = $x->fetch) {
$y->get == $e-- or die "$y $e";
}
# __END__
# $d->config(tls=>1,tls_ca=>'../Neo4j-dist/neo4j-community-4.2.1/certificates/https/neo4j.cert'); # 4
my $m = Neo4j::Driver::Net::HTTP::AnyEvent->new($d);
my $type;
$type = 'application/json';
#$type = 'text/html';
$m->request('GET', 'http://localhost:7474/', undef, $type);
YYY $m->http_header;
XXX $m->fetch_all;
# use AnyEvent;
#
# $| = 1; print "enter your name> ";
#
# my $name;
#
# my $name_ready = AnyEvent->condvar;
#
# my $wait_for_input = AnyEvent->io (
# fh => \*STDIN,
# poll => "r",
# cb => sub {
# $name = <STDIN>;
# $name_ready->send;
# }
# );
#
# # do something else here
#
# # now wait until the name is available:
# $name_ready->recv;
#
# undef $wait_for_input; # watcher no longer needed
#
# print "your name is $name\n";
#
#
#
#
# __END__
use AnyEvent::HTTP;
use Data::Dump;
#STDOUT->autoflush;
my $exit_wait = AnyEvent->condvar;
my $handle = http_request
GET => 'http://localhost:7474/',
sub {
my ($body, $headers) = @_;
dd $headers;
dd $body;
$exit_wait->send;
};
# Do stuff here
$exit_wait->recv;
__END__
use AnyEvent::HTTP;
sub download($$$) {
my ($url, $file, $cb) = @_;
open my $fh, "+<", $file
or die "$file: $!";
my %hdr;
my $ofs = 0;
if (stat $fh and -s _) {
$ofs = -s _;
warn "-s is ", $ofs;
$hdr{"if-unmodified-since"} = AnyEvent::HTTP::format_date +(stat _)[9];
$hdr{"range"} = "bytes=$ofs-";
}
http_get $url,
headers => \%hdr,
on_header => sub {
my ($hdr) = @_;
if ($hdr->{Status} == 200 && $ofs) {
# resume failed
truncate $fh, $ofs = 0;
}
sysseek $fh, $ofs, 0;
1
},
on_body => sub {
my ($data, $hdr) = @_;
if ($hdr->{Status} =~ /^2/) {
length $data == syswrite $fh, $data
or return; # abort on write errors
}
1
},
sub {
my (undef, $hdr) = @_;
my $status = $hdr->{Status};
if (my $time = AnyEvent::HTTP::parse_date $hdr->{"last-modified"}) {
utime $time, $time, $fh;
}
if ($status == 200 || $status == 206 || $status == 416) {
# download ok || resume ok || file already fully downloaded
$cb->(1, $hdr);
} elsif ($status == 412) {
# file has changed while resuming, delete and retry
unlink $file;
$cb->(0, $hdr);
} elsif ($status == 500 or $status == 503 or $status =~ /^59/) {
# retry later
$cb->(0, $hdr);
} else {
$cb->(undef, $hdr);
}
}
;
}
download "http://localhost:7474", "/Users/aj/Sites/Neo4j/driver-perl/ae.txt", sub {
if ($_[0]) {
print "OK!\n";
} elsif (defined $_[0]) {
print "please retry later\n";
} else {
print "ERROR\n";
}
};
AnyEvent->condvar->recv;
__END__
#$d->{die_on_error} = 0;
#$d->{jolt} = 1;
my $s = $d->session;
say $s->server->version, " ", $s->server->protocol, " (", $s->server->{time_diff}, ")";
my $r = $s->run('return "Server OK."');
say $r->fetch->get, $r->isa('Neo4j::Driver::Result::Jolt') ? " (Jolt)" : "";
#XXX $s->{net}->{http_adapter};
# # my $x = $d->session->run('match (n) return n limit 1');
# # say $x->single->get;
# # say $x->single->get->id;
# my $x = $d->session->run([['return {`ab.`}','ab.' => 17],['match (n) return n limit 1']]);
# #XXX $x->[0]->list;
# say $x->[0]->single->get;
# say $x->[1]->single->get->id;
#YYY $s->run('return 0.5, 111, '.(2**31).', {}, []')->fetch;
# my $t = $s->begin_transaction;
# $t->run('RETURN 42');
# $t->rollback;
# my $geo = $s->run('RETURN point({longitude:2, latitude:49})');
# my $geo_z = $s->run('RETURN point({longitude:2, latitude:49, z:80})');
# my $plane = $s->run('RETURN point({x:2, y:49})');
# my $space = $s->run('RETURN point({x:2, y:49, z:80})');
# YYY $_->single->get for ($geo, $geo_z, $plane, $space);
sub active_tx { ref $s->{net}->{active_tx} ? scalar keys %{$s->{net}->{active_tx}} : "$s->{net}->{active_tx}" }
say "commit/rollback: edge cases";
my $t = $s->begin_transaction;
say $t->is_open, ' beginning open';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
$t->rollback; # } 'immediate rollback';
say ! $t->is_open, ' immediate rollback closes';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
# $t->run; # throws } qr/\bclosed\b/, 'run after rollback';
# $t->rollback; # throws } qr/\bclosed\b/, 'rollback after rollback';
# $t->commit; # throws } qr/\bclosed\b/, 'commit after rollback';
$t = $s->begin_transaction;
$t->commit; # } 'immediate commit';
say ! $t->is_open, ' immediate commit closes';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
# $t->run; # throws } qr/\bclosed\b/, 'run after commit';
# $t->commit; # throws } qr/\bclosed\b/, 'commit after commit';
# $t->rollback; # throws } qr/\bclosed\b/, 'rollback after commit';
say "commit/rollback: modify database";
my $entropy = [ 156949788, 54632, 153132456, 424697842 ]; # some constant numbers
$entropy = [ time, $$, srand, int 2**31 * rand ];
$t = $s->begin_transaction;
my $q = <<END;
CREATE (n {entropy: {entropy}}) RETURN id(n) AS node_id
END
$r = $t->run( $q, entropy => $entropy )->single;
say $t->is_open, ' create still open';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
my $node_id = $r->get('node_id');
$q = <<END;
MATCH (n) WHERE id(n) = {node_id} RETURN n.entropy, 0
END
$r = $t->run( $q, node_id => 0 + $node_id );
say $t->is_open, ' match still open';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
die "commit: deemed unsafe; something went seriously wrong" unless defined($node_id) && $r->size;
$t->commit;
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
$t = $s->begin_transaction;
$q = <<END;
MATCH (n) WHERE id(n) = {node_id} RETURN n.entropy, 1
END
$r = $t->run( $q, node_id => 0 + $node_id );
my $commit_error = @$entropy;
foreach my $i (0..3) { # (keys @$entropy)
$commit_error-- if $r->single->get(0)->[$i] == $entropy->[$i];
} # 'verify committed data';
say ! $commit_error, ' commit successful';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
$q = <<END;
MATCH (n) WHERE id(n) = {node_id} DELETE n
END
$t->run( $q, node_id => 0 + $node_id ); # 'try deleting node';
$t->rollback; # } 'rollback';
$t = $s->begin_transaction;
$q = <<END;
MATCH (n) WHERE id(n) = {node_id} RETURN n.entropy, 2
END
$r = $t->run( $q, node_id => 0 + $node_id ); # } 'get data after rollback';
my $rollback_error = @$entropy;
foreach my $i (0..3) { # (keys @$entropy)
$rollback_error-- if $r->single->get(0)->[$i] == $entropy->[$i];
} # 'verify data after rollback';
say ! $rollback_error, ' rollback successful';
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
$t->rollback;
YYY [active_tx($t), $t->{transaction_endpoint}, $t->{commit_endpoint}];
say '';
say '';
my $ttt = $s->begin_transaction;
say "begin_transaction";
say " is_open: ", $ttt->is_open, " active_tx: ", active_tx($s);
$ttt->run;
say "run";
say " is_open: ", $ttt->is_open, " active_tx: ", active_tx($s);
$ttt->rollback;
say "rollback";
say " is_open: ", $ttt->is_open, " active_tx: ", active_tx($s);
$r = $s->begin_transaction;
say "begin_transaction";
say " is_open: ", $r->is_open, " active_tx: ", active_tx($s);
say "";
$r->run;
say "run";
say " is_open: ", $r->is_open, " active_tx: ", active_tx($s);
say "eval:";
eval{$r->run('gg');};print $@;
say "";
say "run('gg')";
say " is_open: ", $r->is_open, " active_tx: ", active_tx($s);
# $ttt = $s->begin_transaction;
# $ttt->run;
# sleep 62;
# eval{$r->run;};
# YYY $ttt->is_open;
# $q = <<END;
# RETURN {a} AS a, {b} AS b
# END
# my ($a, $b) = (17, 19);
$r = $s->run->size;
$r = $s->run->size;
$r = $s->begin_transaction;
$r->run;
say "begin_transaction+run";
say " is_open: ", $r->is_open, " active_tx: ", active_tx($s);
# sleep 65;
say 'ok1';
$r = $s->begin_transaction;
$r->run;
say "begin_transaction+run";
say " is_open: ", $r->is_open, " active_tx: ", active_tx($s);
#XXX $s;
YYY $s->run('return 9.9^999, sqrt(-3)')->single->data;
sub aa {
$_[0]->is_open;
my %a = ( %{shift->{net}->{active_tx}} );
$a{$_} = "$a{$_}" for keys %a;
YYY \%a;
}
say "0";
for my $i (1..15) {
sleep 6;
aa($r);
print $r->run('RETURN $i', i => $i)->single->get;
say ": " . Time::Piece->new;
}
__END__
# Jolt
use HTTP::Tiny;
use JSON::MaybeXS;
my $http = HTTP::Tiny->new(
agent => "Test/0.0.0 ",
default_headers => {
Accept => "application/json; q=0.5, application/vnd.neo4j.jolt+json-seq; q=0.8, application/vnd.neo4j.jolt+json-seq; strict=true; q=1",
# Accept => "application/json; q=0.999, application/vnd.neo4j.jolt+json-seq; q=1, text/html; q=0",
},
);
my $json = {
statements => [
{
includeStats => \1,
statement => 'unwind [3,5,7] as x return x, 42, "hi"',
resultDataContents => ["row","rest","graph"],
},
{
includeStats => \1,
statement => 'unwind [0,2,4] as x return x, 42, "hi"',
resultDataContents => ["row","rest","graph"],
},
],
};
my $opts = {
content => encode_json($json),
headers => {
'Content-Type' => 'application/json',
},
};
my $authority = 'neo4j:pass@localhost:7474';
#my $path = '/db/data/transaction'; # 2/3
my $path = '/db/neo4j/tx'; # 4
my $res = $http->post("http://$authority$path", $opts);
#my $res = $http->get("http://$authority/");
YYY $res->{headers};
print $res->{content};
# json-seq:
# https://tools.ietf.org/html/rfc7464
__END__
# my $xxxx = $s->run("RETURN date('+2015-W13-4') as theDate");
# "theDate"
# "struct<0x44>(16520)"
# Date::Structure(
# days::Integer,
# )
# The days are days since the Unix epoch.
# pentland:driver-perl aj$ ./jolt.pl
# This is Neo4j::Driver DEV
# Neo4j/4.2.1 HTTP/1.1
# Server OK.
# bless({ data => "2015-03-26", type => "date" }, "Neo4j::Driver::Type::Temporal")
# at ./jolt.pl line 37
# pentland:driver-perl aj$ ./jolt.pl
# This is Neo4j::Driver DEV
# Neo4j/4.2.1 HTTP/1.1
# Server OK. (Jolt)
# { T => "2015-03-26" }
# at ./jolt.pl line 37
perl -MJSON::XS -E 'say encode_json {statements=>[{statement=>"return log(-1) as FloatNaN, 9.9^999 as FloatPosInf, -9.9^999 as FloatNegInf, -0.00 as FloatNegZero, 0.00 as FloatPosZero"}]}'
{"statements":[{"statement":"return log(-1) as FloatNaN, 9.9^999 as FloatPosInf, -9.9^999 as FloatNegInf, -0.00 as FloatNegZero, 0.00 as FloatPosZero"}]}
curl -u neo4j:pass -fid '{"statements":[{"statement":"return log(-1) as FloatNaN, 9.9^999 as FloatPosInf, -9.9^999 as FloatNegInf, -0.00 as FloatNegZero, 0.00 as FloatPosZero"}]}' -H "Content-Type:application/json" http://localhost:7474/db/neo4j/tx/commit ; echo
HTTP/1.1 200 OK
Date: Tue, 22 Jun 2021 16:23:10 GMT
Access-Control-Allow-Origin: *
Content-Type: application/json
Content-Length: 150
{"results":[{"columns":["FloatNaN","FloatPosInf","FloatNegInf","FloatNegZero","FloatPosZero"],"data":[{"row":["NaN","Infinity","-Infinity",-0.0,0.0],"meta":[null,null,null,null,null]}]}],"errors":[]}
curl -u neo4j:pass -fid '{"statements":[{"statement":"return log(-1) as FloatNaN, 9.9^999 as FloatPosInf, -9.9^999 as FloatNegInf, -0.00 as FloatNegZero, 0.00 as FloatPosZero"}]}' -H "Content-Type:application/json" -H "Accept:application/vnd.neo4j.jolt" http://localhost:7474/db/neo4j/tx/commit ; echo
HTTP/1.1 200 OK
Date: Tue, 22 Jun 2021 16:23:46 GMT
Access-Control-Allow-Origin: *
Content-Type: application/vnd.neo4j.jolt
Content-Length: 148
{"header":{"fields":["FloatNaN","FloatPosInf","FloatNegInf","FloatNegZero","FloatPosZero"]}}
{"data":[{"R":"NaN"},{"R":"Infinity"},{"R":"-Infinity"},{"R":"-0.0"},{"R":"0.0"}]}
{"summary":{}}
{"info":{}}
perl -MJSON::XS -E 'say encode_json {statements=>[{statement=>"UNWIND [0.00, -0.00, 9.9^999, -9.9^999, log(-1)] as x RETURN x"}]}'
{"statements":[{"statement":"UNWIND [0.00, -0.00, 9.9^999, -9.9^999, log(-1)] as x RETURN x"}]}
curl -u neo4j:pass -fid '{"statements":[{"statement":"UNWIND [0.00, -0.00, 9.9^999, -9.9^999, log(-1)] as x RETURN x"}]}' -H "Content-Type:application/json" -H "Accept:application/vnd.neo4j.jolt" http://localhost:7474/db/neo4j/tx/commit ; echo
HTTP/1.1 200 OK
Date: Tue, 22 Jun 2021 17:39:26 GMT
Access-Control-Allow-Origin: *
Content-Type: application/vnd.neo4j.jolt
Content-Length: 182
{"header":{"fields":["x"]}}
{"data":[{"R":"0.0"}]}
{"data":[{"R":"-0.0"}]}
{"data":[{"R":"Infinity"}]}
{"data":[{"R":"-Infinity"}]}
{"data":[{"R":"NaN"}]}
{"summary":{}}
{"info":{}}