neo4j_bolt 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/lib/neo4j_bolt/version.rb +1 -1
- data/lib/neo4j_bolt.rb +136 -3
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 86471d4b049b4054a378f5084ae13dda181bd818a205ee4a67e6aa4bc5d0607f
|
4
|
+
data.tar.gz: b1e855e4689946026eaa7971acb730577a57973b02605c893916d15e2438b1f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4e414cb24861f58ae67f2a5c92da46f720c861fd49131befa16a9c046ea557d1e31fb40130fe892aa91b38d0ec3de881d4da94de4fde4a5288ab0add379c33f
|
7
|
+
data.tar.gz: be8ed37b6ea0a7e358c25257eea3be90f78c2fd1342a4666bc244a5a379e241780a6df01d4005c4b26a4986a2fd24cd0a65f7569f4de19bcbf4a65517a33aa8a
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Neo4jBolt
|
2
2
|
|
3
|
-
A Neo4j/Bolt driver written in pure Ruby. Currently only supporting Neo4j 4.4.
|
3
|
+
A Neo4j/Bolt driver written in pure Ruby. Currently only supporting Neo4j 4.4. Caution: This gem is not feature complete, and also the documention is not complete yet.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
data/lib/neo4j_bolt/version.rb
CHANGED
data/lib/neo4j_bolt.rb
CHANGED
@@ -11,6 +11,7 @@ module Neo4jBolt
|
|
11
11
|
self.bolt_port = 7687
|
12
12
|
|
13
13
|
NEO4J_DEBUG = 0
|
14
|
+
CONSTRAINT_INDEX_PREFIX = 'neo4j_bolt_'
|
14
15
|
|
15
16
|
module ServerState
|
16
17
|
DISCONNECTED = 0
|
@@ -47,6 +48,7 @@ module Neo4jBolt
|
|
47
48
|
class Error < StandardError; end
|
48
49
|
class IntegerOutOfRangeError < Error; end
|
49
50
|
class SyntaxError < Error; end
|
51
|
+
class ConstraintValidationFailedError < Error; end
|
50
52
|
class ExpectedOneResultError < Error; end
|
51
53
|
class UnexpectedServerResponse < Error
|
52
54
|
def initialize(token)
|
@@ -583,6 +585,8 @@ module Neo4jBolt
|
|
583
585
|
def bolt_error(code, message)
|
584
586
|
if code == 'Neo.ClientError.Statement.SyntaxError'
|
585
587
|
SyntaxError.new(message)
|
588
|
+
elsif code == 'Neo.ClientError.Schema.ConstraintValidationFailed'
|
589
|
+
ConstraintValidationFailedError.new(message)
|
586
590
|
else
|
587
591
|
Error.new("#{code}\n#{message}")
|
588
592
|
end
|
@@ -827,6 +831,50 @@ module Neo4jBolt
|
|
827
831
|
end
|
828
832
|
rows.first
|
829
833
|
end
|
834
|
+
|
835
|
+
def setup_constraints_and_indexes(constraints, indexes)
|
836
|
+
wanted_constraints = Set.new()
|
837
|
+
wanted_indexes = Set.new()
|
838
|
+
# STDERR.puts "Setting up constraints and indexes..."
|
839
|
+
constraints.each do |constraint|
|
840
|
+
unless constraint =~ /\w+\/\w+/
|
841
|
+
raise "Unexpected constraint format: #{constraint}"
|
842
|
+
end
|
843
|
+
constraint_name = "#{CONSTRAINT_INDEX_PREFIX}#{constraint.gsub('/', '_')}"
|
844
|
+
wanted_constraints << constraint_name
|
845
|
+
label = constraint.split('/').first
|
846
|
+
property = constraint.split('/').last
|
847
|
+
query = "CREATE CONSTRAINT #{constraint_name} IF NOT EXISTS FOR (n:#{label}) REQUIRE n.#{property} IS UNIQUE"
|
848
|
+
# STDERR.puts query
|
849
|
+
neo4j_query(query)
|
850
|
+
end
|
851
|
+
indexes.each do |index|
|
852
|
+
unless index =~ /\w+\/\w+/
|
853
|
+
raise "Unexpected index format: #{index}"
|
854
|
+
end
|
855
|
+
index_name = "#{CONSTRAINT_INDEX_PREFIX}#{index.gsub('/', '_')}"
|
856
|
+
wanted_indexes << index_name
|
857
|
+
label = index.split('/').first
|
858
|
+
property = index.split('/').last
|
859
|
+
query = "CREATE INDEX #{index_name} IF NOT EXISTS FOR (n:#{label}) ON (n.#{property})"
|
860
|
+
# STDERR.puts query
|
861
|
+
neo4j_query(query)
|
862
|
+
end
|
863
|
+
neo4j_query("SHOW ALL CONSTRAINTS").each do |row|
|
864
|
+
next unless row['name'].index(CONSTRAINT_INDEX_PREFIX) == 0
|
865
|
+
next if wanted_constraints.include?(row['name'])
|
866
|
+
query = "DROP CONSTRAINT #{row['name']}"
|
867
|
+
# STDERR.puts query
|
868
|
+
neo4j_query(query)
|
869
|
+
end
|
870
|
+
neo4j_query("SHOW ALL INDEXES").each do |row|
|
871
|
+
next unless row['name'].index(CONSTRAINT_INDEX_PREFIX) == 0
|
872
|
+
next if wanted_indexes.include?(row['name']) || wanted_constraints.include?(row['name'])
|
873
|
+
query = "DROP INDEX #{row['name']}"
|
874
|
+
# STDERR.puts query
|
875
|
+
neo4j_query(query)
|
876
|
+
end
|
877
|
+
end
|
830
878
|
end
|
831
879
|
|
832
880
|
def transaction(&block)
|
@@ -861,7 +909,7 @@ module Neo4jBolt
|
|
861
909
|
end
|
862
910
|
end
|
863
911
|
|
864
|
-
def dump_database(
|
912
|
+
def dump_database(io)
|
865
913
|
tr_id = {}
|
866
914
|
id = 0
|
867
915
|
neo4j_query("MATCH (n) RETURN n ORDER BY ID(n);") do |row|
|
@@ -871,7 +919,7 @@ module Neo4jBolt
|
|
871
919
|
:labels => row['n'].labels,
|
872
920
|
:properties => row['n']
|
873
921
|
}
|
874
|
-
|
922
|
+
io.puts "n #{node.to_json}"
|
875
923
|
id += 1
|
876
924
|
end
|
877
925
|
neo4j_query("MATCH ()-[r]->() RETURN r;") do |row|
|
@@ -881,14 +929,99 @@ module Neo4jBolt
|
|
881
929
|
:type => row['r'].type,
|
882
930
|
:properties => row['r']
|
883
931
|
}
|
884
|
-
|
932
|
+
io.puts "r #{rel.to_json}"
|
885
933
|
end
|
886
934
|
end
|
887
935
|
|
936
|
+
def load_database_dump(io, force_append: false)
|
937
|
+
unless force_append
|
938
|
+
transaction do
|
939
|
+
node_count = neo4j_query_expect_one('MATCH (n) RETURN COUNT(n) as count;')['count']
|
940
|
+
unless node_count == 0
|
941
|
+
raise "Error: There are nodes in this database, exiting now."
|
942
|
+
end
|
943
|
+
end
|
944
|
+
end
|
945
|
+
n_count = 0
|
946
|
+
r_count = 0
|
947
|
+
node_tr = {}
|
948
|
+
node_batch_by_label = {}
|
949
|
+
relationship_batch_by_type = {}
|
950
|
+
io.each_line do |line|
|
951
|
+
line.strip!
|
952
|
+
next if line.empty?
|
953
|
+
if line[0] == 'n'
|
954
|
+
line = line[2, line.size - 2]
|
955
|
+
node = JSON.parse(line)
|
956
|
+
label_key = node['labels'].sort.join('/')
|
957
|
+
node_batch_by_label[label_key] ||= []
|
958
|
+
node_batch_by_label[label_key] << node
|
959
|
+
elsif line[0] == 'r'
|
960
|
+
line = line[2, line.size - 2]
|
961
|
+
relationship = JSON.parse(line)
|
962
|
+
relationship_batch_by_type[relationship['type']] ||= []
|
963
|
+
relationship_batch_by_type[relationship['type']] << relationship
|
964
|
+
else
|
965
|
+
STDERR.puts "Invalid entry: #{line}"
|
966
|
+
exit(1)
|
967
|
+
end
|
968
|
+
end
|
969
|
+
node_batch_by_label.each_pair do |label_key, batch|
|
970
|
+
while !batch.empty? do
|
971
|
+
slice = []
|
972
|
+
json_size = 0
|
973
|
+
while (!batch.empty?) && json_size < 0x20000 && slice.size < 256
|
974
|
+
x = batch.shift
|
975
|
+
slice << x
|
976
|
+
json_size += x.to_json.size
|
977
|
+
end
|
978
|
+
ids = neo4j_query(<<~END_OF_QUERY, {:properties => slice.map { |x| x['properties']}})
|
979
|
+
UNWIND $properties AS props
|
980
|
+
CREATE (n:#{slice.first['labels'].join(':')})
|
981
|
+
SET n = props
|
982
|
+
RETURN ID(n) AS id;
|
983
|
+
END_OF_QUERY
|
984
|
+
slice.each.with_index do |node, i|
|
985
|
+
node_tr[node['id']] = ids[i]['id']
|
986
|
+
end
|
987
|
+
n_count += slice.size
|
988
|
+
STDERR.print "\rLoaded #{n_count} nodes, #{r_count} relationships..."
|
989
|
+
end
|
990
|
+
end
|
991
|
+
relationship_batch_by_type.each_pair do |rel_type, batch|
|
992
|
+
batch.each_slice(256) do |slice|
|
993
|
+
slice.map! do |rel|
|
994
|
+
rel['from'] = node_tr[rel['from']]
|
995
|
+
rel['to'] = node_tr[rel['to']]
|
996
|
+
rel
|
997
|
+
end
|
998
|
+
count = neo4j_query_expect_one(<<~END_OF_QUERY, {:slice => slice})['count_r']
|
999
|
+
UNWIND $slice AS props
|
1000
|
+
MATCH (from), (to) WHERE ID(from) = props.from AND ID(to) = props.to
|
1001
|
+
CREATE (from)-[r:#{rel_type}]->(to)
|
1002
|
+
SET r = props.properties
|
1003
|
+
RETURN COUNT(r) AS count_r, COUNT(from) AS count_from, COUNT(to) AS count_to;
|
1004
|
+
END_OF_QUERY
|
1005
|
+
if count != slice.size
|
1006
|
+
raise "Ooops... expected #{slice.size} relationships, got #{count}."
|
1007
|
+
end
|
1008
|
+
r_count += slice.size
|
1009
|
+
STDERR.print "\rLoaded #{n_count} nodes, #{r_count} relationships..."
|
1010
|
+
end
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
STDERR.puts
|
1014
|
+
end
|
1015
|
+
|
888
1016
|
def cleanup_neo4j
|
889
1017
|
if @bolt_socket
|
890
1018
|
@bolt_socket.disconnect()
|
891
1019
|
@bolt_socket = nil
|
892
1020
|
end
|
893
1021
|
end
|
1022
|
+
|
1023
|
+
def setup_constraints_and_indexes(constraints, indexes)
|
1024
|
+
@bolt_socket ||= BoltSocket.new()
|
1025
|
+
@bolt_socket.setup_constraints_and_indexes(constraints, indexes)
|
1026
|
+
end
|
894
1027
|
end
|