neo4j-core 6.1.6 → 7.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|