neo4j-core 6.1.6 → 7.0.0.alpha.1
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 +4 -9
- data/README.md +48 -0
- data/lib/neo4j-core.rb +23 -0
- data/lib/neo4j-core/helpers.rb +8 -0
- data/lib/neo4j-core/query.rb +23 -20
- data/lib/neo4j-core/query_clauses.rb +18 -32
- data/lib/neo4j-core/query_find_in_batches.rb +3 -1
- data/lib/neo4j-core/version.rb +1 -1
- data/lib/neo4j-embedded/cypher_response.rb +4 -0
- data/lib/neo4j-embedded/embedded_database.rb +3 -5
- data/lib/neo4j-embedded/embedded_node.rb +4 -4
- data/lib/neo4j-embedded/embedded_session.rb +21 -10
- data/lib/neo4j-embedded/embedded_transaction.rb +4 -10
- data/lib/neo4j-server/cypher_node.rb +5 -4
- data/lib/neo4j-server/cypher_relationship.rb +3 -3
- data/lib/neo4j-server/cypher_response.rb +4 -0
- data/lib/neo4j-server/cypher_session.rb +31 -22
- data/lib/neo4j-server/cypher_transaction.rb +23 -15
- data/lib/neo4j-server/resource.rb +3 -4
- data/lib/neo4j/core/cypher_session.rb +17 -9
- data/lib/neo4j/core/cypher_session/adaptors.rb +116 -33
- data/lib/neo4j/core/cypher_session/adaptors/bolt.rb +331 -0
- data/lib/neo4j/core/cypher_session/adaptors/bolt/chunk_writer_io.rb +76 -0
- data/lib/neo4j/core/cypher_session/adaptors/bolt/pack_stream.rb +288 -0
- data/lib/neo4j/core/cypher_session/adaptors/embedded.rb +60 -29
- data/lib/neo4j/core/cypher_session/adaptors/has_uri.rb +63 -0
- data/lib/neo4j/core/cypher_session/adaptors/http.rb +123 -119
- data/lib/neo4j/core/cypher_session/responses.rb +17 -2
- data/lib/neo4j/core/cypher_session/responses/bolt.rb +135 -0
- data/lib/neo4j/core/cypher_session/responses/embedded.rb +46 -11
- data/lib/neo4j/core/cypher_session/responses/http.rb +49 -40
- data/lib/neo4j/core/cypher_session/transactions.rb +33 -0
- data/lib/neo4j/core/cypher_session/transactions/bolt.rb +36 -0
- data/lib/neo4j/core/cypher_session/transactions/embedded.rb +32 -0
- data/lib/neo4j/core/cypher_session/transactions/http.rb +52 -0
- data/lib/neo4j/core/instrumentable.rb +2 -2
- data/lib/neo4j/core/label.rb +182 -0
- data/lib/neo4j/core/node.rb +8 -3
- data/lib/neo4j/core/relationship.rb +12 -4
- data/lib/neo4j/entity_equality.rb +1 -1
- data/lib/neo4j/session.rb +4 -5
- data/lib/neo4j/transaction.rb +108 -72
- data/neo4j-core.gemspec +6 -6
- metadata +34 -40
@@ -7,8 +7,6 @@ module Neo4j
|
|
7
7
|
MAP = {}
|
8
8
|
|
9
9
|
class Base
|
10
|
-
class CypherError < StandardError; end
|
11
|
-
|
12
10
|
include Enumerable
|
13
11
|
|
14
12
|
def each
|
@@ -17,6 +15,23 @@ module Neo4j
|
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
18
|
+
def wrap_by_level(none_value)
|
19
|
+
case @wrap_level
|
20
|
+
when :none
|
21
|
+
if none_value.is_a?(Array)
|
22
|
+
none_value.map(&:symbolize_keys)
|
23
|
+
else
|
24
|
+
none_value.symbolize_keys
|
25
|
+
end
|
26
|
+
when :core_entity
|
27
|
+
yield
|
28
|
+
when :proc
|
29
|
+
yield.wrap
|
30
|
+
else
|
31
|
+
fail ArgumentError, "Inalid wrap_level: #{@wrap_level.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
20
35
|
def results
|
21
36
|
fail '#results not implemented!'
|
22
37
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'neo4j/core/cypher_session/responses'
|
2
|
+
require 'active_support/core_ext/hash/keys'
|
3
|
+
|
4
|
+
module Neo4j
|
5
|
+
module Core
|
6
|
+
class CypherSession
|
7
|
+
module Responses
|
8
|
+
class Bolt < Base
|
9
|
+
attr_reader :results, :result_info
|
10
|
+
|
11
|
+
def initialize(queries, flush_messages_proc, options = {})
|
12
|
+
@wrap_level = options[:wrap_level] || Neo4j::Core::Config.wrapping_level
|
13
|
+
|
14
|
+
@results = queries.map do
|
15
|
+
fields, result_messages, _footer_messages = extract_message_groups(flush_messages_proc)
|
16
|
+
# @result_info = footer_messages[0].args[0]
|
17
|
+
|
18
|
+
data = result_messages.map do |result_message|
|
19
|
+
validate_message_type!(result_message, :record)
|
20
|
+
|
21
|
+
result_message.args[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
result_from_data(fields, data)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def result_from_data(columns, entities_data)
|
29
|
+
rows = entities_data.map do |entity_data|
|
30
|
+
wrap_entity(entity_data)
|
31
|
+
end
|
32
|
+
|
33
|
+
Result.new(columns, rows)
|
34
|
+
end
|
35
|
+
|
36
|
+
def wrap_entity(entity_data)
|
37
|
+
case entity_data
|
38
|
+
when Array
|
39
|
+
entity_data.map(&method(:wrap_entity))
|
40
|
+
when PackStream::Structure
|
41
|
+
wrap_structure(entity_data)
|
42
|
+
when Hash
|
43
|
+
entity_data.each_with_object({}) do |(k, v), result|
|
44
|
+
result[k.to_sym] = wrap_entity(v)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
entity_data
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def extract_message_groups(flush_messages_proc)
|
54
|
+
fields_messages = flush_messages_proc.call
|
55
|
+
validate_message_type!(fields_messages[0], :success)
|
56
|
+
|
57
|
+
result_messages = []
|
58
|
+
while (messages = flush_messages_proc.call)[0].type != :success
|
59
|
+
result_messages.concat(messages)
|
60
|
+
end
|
61
|
+
|
62
|
+
[fields_messages[0].args[0]['fields'],
|
63
|
+
result_messages,
|
64
|
+
messages]
|
65
|
+
end
|
66
|
+
|
67
|
+
def wrap_structure(structure)
|
68
|
+
case structure.signature
|
69
|
+
when 0x4E then wrap_node(*structure.list)
|
70
|
+
when 0x52 then wrap_relationship(*structure.list)
|
71
|
+
when 0x72 then wrap_unbound_relationship(*structure.list)
|
72
|
+
when 0x50 then wrap_path(*structure.list)
|
73
|
+
else
|
74
|
+
fail CypherError, "Unsupported structure signature: #{structure.signature}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def wrap_by_level(none_value)
|
79
|
+
case @wrap_level
|
80
|
+
when :none
|
81
|
+
if none_value.is_a?(Array)
|
82
|
+
none_value.map(&:symbolize_keys)
|
83
|
+
else
|
84
|
+
none_value.symbolize_keys
|
85
|
+
end
|
86
|
+
when :core_entity
|
87
|
+
yield
|
88
|
+
when :proc
|
89
|
+
yield.wrap
|
90
|
+
else
|
91
|
+
fail ArgumentError, "Inalid wrap_level: #{@wrap_level.inspect}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def wrap_node(id, labels, properties)
|
96
|
+
wrap_by_level(properties) { ::Neo4j::Core::Node.new(id, labels, properties) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def wrap_relationship(id, from_node_id, to_node_id, type, properties)
|
100
|
+
wrap_by_level(properties) { ::Neo4j::Core::Relationship.new(id, type, properties, from_node_id, to_node_id) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def wrap_unbound_relationship(id, type, properties)
|
104
|
+
wrap_by_level(properties) { ::Neo4j::Core::Relationship.new(id, type, properties) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def wrap_path(nodes, relationships, directions)
|
108
|
+
none_value = nodes.zip(relationships).flatten.compact.map { |obj| obj.list.last }
|
109
|
+
wrap_by_level(none_value) do
|
110
|
+
::Neo4j::Core::Path.new(nodes.map(&method(:wrap_entity)),
|
111
|
+
relationships.map(&method(:wrap_entity)),
|
112
|
+
directions.map(&method(:wrap_direction)))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def wrap_direction(_direction_int)
|
117
|
+
''
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_message_type!(message, type)
|
121
|
+
case message.type
|
122
|
+
when type
|
123
|
+
return
|
124
|
+
when :failure
|
125
|
+
data = message.args[0]
|
126
|
+
throw :cypher_bolt_failure, data
|
127
|
+
else
|
128
|
+
fail "Unexpected message type: #{message.type} (#{message.inspect})"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -7,7 +7,11 @@ module Neo4j
|
|
7
7
|
class Embedded < Base
|
8
8
|
attr_reader :results, :request_data
|
9
9
|
|
10
|
-
def initialize(execution_results)
|
10
|
+
def initialize(execution_results, options = {})
|
11
|
+
# validate_response!(execution_results)
|
12
|
+
|
13
|
+
@wrap_level = options[:wrap_level] || Neo4j::Core::Config.wrapping_level
|
14
|
+
|
11
15
|
@results = execution_results.map do |execution_result|
|
12
16
|
result_from_execution_result(execution_result)
|
13
17
|
end
|
@@ -26,39 +30,66 @@ module Neo4j
|
|
26
30
|
def wrap_entity(entity)
|
27
31
|
if entity.is_a?(Array) ||
|
28
32
|
entity.is_a?(Java::ScalaCollectionConvert::Wrappers::SeqWrapper)
|
29
|
-
entity.to_a.map
|
30
|
-
|
31
|
-
|
33
|
+
entity.to_a.map(&method(:wrap_entity))
|
34
|
+
else
|
35
|
+
_wrap_entity(entity)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def _wrap_entity(entity)
|
40
|
+
case @wrap_level
|
41
|
+
when :none then wrap_value(entity)
|
42
|
+
when :core_entity, :proc
|
43
|
+
if type = type_for_entity(entity)
|
44
|
+
result = send("wrap_#{type}", entity)
|
45
|
+
|
46
|
+
@wrap_level == :proc ? result.wrap : result
|
47
|
+
else
|
48
|
+
wrap_value(entity)
|
49
|
+
end
|
50
|
+
else
|
51
|
+
fail ArgumentError, "Inalid wrap_level: #{@wrap_level.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def type_for_entity(entity)
|
56
|
+
if entity.is_a?(Java::OrgNeo4jKernelImplCore::NodeProxy)
|
57
|
+
:node
|
32
58
|
elsif entity.is_a?(Java::OrgNeo4jKernelImplCore::RelationshipProxy)
|
33
|
-
|
59
|
+
:relationship
|
34
60
|
elsif entity.respond_to?(:path_entities)
|
35
|
-
|
36
|
-
else
|
37
|
-
wrap_value(entity)
|
61
|
+
:path
|
38
62
|
end
|
39
63
|
end
|
40
64
|
|
41
65
|
def wrap_node(entity)
|
42
66
|
::Neo4j::Core::Node.new(entity.get_id,
|
43
67
|
entity.get_labels.map(&:to_s),
|
44
|
-
get_entity_properties(entity))
|
68
|
+
get_entity_properties(entity))
|
45
69
|
end
|
46
70
|
|
47
71
|
def wrap_relationship(entity)
|
48
72
|
::Neo4j::Core::Relationship.new(entity.get_id,
|
49
73
|
entity.get_type.name,
|
50
|
-
get_entity_properties(entity)
|
74
|
+
get_entity_properties(entity),
|
75
|
+
entity.get_start_node.neo_id,
|
76
|
+
entity.get_end_node.neo_id)
|
51
77
|
end
|
52
78
|
|
53
79
|
def wrap_path(entity)
|
54
80
|
::Neo4j::Core::Path.new(entity.nodes.map(&method(:wrap_node)),
|
55
81
|
entity.relationships.map(&method(:wrap_relationship)),
|
56
|
-
nil)
|
82
|
+
nil)
|
57
83
|
end
|
58
84
|
|
59
85
|
def wrap_value(entity)
|
60
86
|
if entity.is_a?(Java::ScalaCollectionConvert::Wrappers::MapWrapper)
|
61
87
|
entity.each_with_object({}) { |(k, v), r| r[k.to_sym] = v }
|
88
|
+
elsif entity.is_a?(Java::OrgNeo4jKernelImplCore::NodeProxy) ||
|
89
|
+
entity.is_a?(Java::OrgNeo4jKernelImplCore::RelationshipProxy)
|
90
|
+
entity.property_keys.each_with_object({}) { |key, hash| hash[key.to_sym] = entity.get_property(key) }
|
91
|
+
elsif entity.respond_to?(:path_entities)
|
92
|
+
entity.to_a.map(&method(:wrap_value))
|
62
93
|
else
|
63
94
|
# Convert from Java?
|
64
95
|
entity
|
@@ -70,6 +101,10 @@ module Neo4j
|
|
70
101
|
result[key.to_sym] = entity.get_property(key)
|
71
102
|
end
|
72
103
|
end
|
104
|
+
|
105
|
+
def validate_response!(_execution_results)
|
106
|
+
require 'pry'
|
107
|
+
end
|
73
108
|
end
|
74
109
|
end
|
75
110
|
end
|
@@ -7,11 +7,14 @@ module Neo4j
|
|
7
7
|
class HTTP < Base
|
8
8
|
attr_reader :results, :request_data
|
9
9
|
|
10
|
-
def initialize(faraday_response,
|
10
|
+
def initialize(faraday_response, options = {})
|
11
|
+
@faraday_response = faraday_response
|
11
12
|
@request_data = request_data
|
12
13
|
|
13
14
|
validate_faraday_response!(faraday_response)
|
14
15
|
|
16
|
+
@wrap_level = options[:wrap_level] || Neo4j::Core::Config.wrapping_level
|
17
|
+
|
15
18
|
@results = faraday_response.body[:results].map do |result_data|
|
16
19
|
result_from_data(result_data[:columns], result_data[:data])
|
17
20
|
end
|
@@ -25,79 +28,85 @@ module Neo4j
|
|
25
28
|
Result.new(columns, rows)
|
26
29
|
end
|
27
30
|
|
28
|
-
def wrap_entity(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
row_datum
|
31
|
+
def wrap_entity(row_datum, rest_datum)
|
32
|
+
case
|
33
|
+
when rest_datum.is_a?(Array)
|
34
|
+
row_datum.zip(rest_datum).map { |row, rest| wrap_entity(row, rest) }
|
35
|
+
when ident = identify_entity(rest_datum)
|
36
|
+
send("wrap_#{ident}", rest_datum, row_datum)
|
37
|
+
when rest_datum.is_a?(Hash)
|
38
|
+
rest_datum.each_with_object({}) do |(k, v), result|
|
39
|
+
result[k.to_sym] = wrap_entity(row_datum[k], v)
|
38
40
|
end
|
41
|
+
else
|
42
|
+
row_datum
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
42
46
|
private
|
43
47
|
|
44
48
|
def identify_entity(rest_datum)
|
49
|
+
return if !rest_datum.is_a?(Hash)
|
45
50
|
self_string = rest_datum[:self]
|
46
51
|
if self_string
|
47
52
|
type = self_string.split('/')[-2]
|
48
|
-
|
49
|
-
:node
|
50
|
-
elsif type == 'relationship'
|
51
|
-
:relationship
|
52
|
-
end
|
53
|
+
type.to_sym if %w(node relationship).include?(type)
|
53
54
|
elsif [:nodes, :relationships, :start, :end, :length].all? { |k| rest_datum.key?(k) }
|
54
55
|
:path
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
|
-
def wrap_node(rest_datum,
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
def wrap_node(rest_datum, row_datum)
|
60
|
+
wrap_by_level(row_datum) do
|
61
|
+
metadata_data = rest_datum[:metadata]
|
62
|
+
::Neo4j::Core::Node.new(id_from_rest_datum(rest_datum),
|
63
|
+
metadata_data && metadata_data[:labels],
|
64
|
+
rest_datum[:data])
|
65
|
+
end
|
63
66
|
end
|
64
67
|
|
65
|
-
def wrap_relationship(rest_datum,
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
def wrap_relationship(rest_datum, row_datum)
|
69
|
+
wrap_by_level(row_datum) do
|
70
|
+
metadata_data = rest_datum[:metadata]
|
71
|
+
::Neo4j::Core::Relationship.new(id_from_rest_datum(rest_datum),
|
72
|
+
metadata_data && metadata_data[:type],
|
73
|
+
rest_datum[:data],
|
74
|
+
id_from_url(rest_datum[:start]),
|
75
|
+
id_from_url(rest_datum[:end]))
|
76
|
+
end
|
70
77
|
end
|
71
78
|
|
72
79
|
def wrap_path(rest_datum, row_datum)
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
80
|
+
wrap_by_level(row_datum) do
|
81
|
+
nodes = rest_datum[:nodes].each_with_index.map do |url, i|
|
82
|
+
Node.from_url(url, row_datum[2 * i])
|
83
|
+
end
|
84
|
+
relationships = rest_datum[:relationships].each_with_index.map do |url, i|
|
85
|
+
Relationship.from_url(url, row_datum[(2 * i) + 1])
|
86
|
+
end
|
79
87
|
|
80
|
-
|
88
|
+
::Neo4j::Core::Path.new(nodes, relationships, rest_datum[:directions])
|
89
|
+
end
|
81
90
|
end
|
82
91
|
|
83
92
|
def id_from_rest_datum(rest_datum)
|
84
93
|
if rest_datum[:metadata]
|
85
94
|
rest_datum[:metadata][:id]
|
86
95
|
else
|
87
|
-
rest_datum[:self]
|
96
|
+
id_from_url(rest_datum[:self])
|
88
97
|
end
|
89
98
|
end
|
90
99
|
|
100
|
+
def id_from_url(url)
|
101
|
+
url.split('/').last.to_i
|
102
|
+
end
|
103
|
+
|
91
104
|
def validate_faraday_response!(faraday_response)
|
92
105
|
if faraday_response.body.is_a?(Hash) && error = faraday_response.body[:errors][0]
|
93
|
-
error
|
94
|
-
Request to: #{ANSI::BOLD}#{faraday_response.env.url}#{ANSI::CLEAR}
|
95
|
-
#{ANSI::CYAN}#{error[:code]}#{ANSI::CLEAR}: #{error[:message]}
|
96
|
-
ERROR
|
97
|
-
fail CypherError, error
|
106
|
+
fail CypherError.new_from(error[:code], error[:message], error[:stack_trace])
|
98
107
|
end
|
99
108
|
|
100
|
-
return if (200..299).
|
109
|
+
return if (200..299).cover?(status = faraday_response.status)
|
101
110
|
|
102
111
|
fail CypherError, "Expected 200-series response for #{faraday_response.env.url} (got #{status})"
|
103
112
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Core
|
3
|
+
class CypherSession
|
4
|
+
module Transactions
|
5
|
+
class Base < Neo4j::Transaction::Base
|
6
|
+
def query(*args)
|
7
|
+
options = if args[0].is_a?(::Neo4j::Core::Query)
|
8
|
+
args[1] ||= {}
|
9
|
+
else
|
10
|
+
args[1] ||= {}
|
11
|
+
args[2] ||= {}
|
12
|
+
end
|
13
|
+
options[:transaction] ||= self
|
14
|
+
|
15
|
+
adaptor.query(@session, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def queries(options = {}, &block)
|
19
|
+
adaptor.queries(@session, {transaction: self}.merge(options), &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
# Because we're inheriting from the old Transaction class
|
25
|
+
# but the new adaptors work much like the old sessions
|
26
|
+
def adaptor
|
27
|
+
@session.adaptor
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'neo4j/core/cypher_session/transactions'
|
2
|
+
|
3
|
+
module Neo4j
|
4
|
+
module Core
|
5
|
+
class CypherSession
|
6
|
+
module Transactions
|
7
|
+
class Bolt < Base
|
8
|
+
def initialize(*args)
|
9
|
+
super
|
10
|
+
|
11
|
+
tx_query('BEGIN') if root?
|
12
|
+
end
|
13
|
+
|
14
|
+
def commit
|
15
|
+
tx_query('COMMIT') if root?
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete
|
19
|
+
tx_query('ROLLBACK')
|
20
|
+
end
|
21
|
+
|
22
|
+
def started?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def tx_query(cypher)
|
29
|
+
query = Adaptors::Base::Query.new(cypher, {}, cypher)
|
30
|
+
adaptor.send(:query_set, self, [query], skip_instrumentation: true)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|