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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -9
  3. data/README.md +48 -0
  4. data/lib/neo4j-core.rb +23 -0
  5. data/lib/neo4j-core/helpers.rb +8 -0
  6. data/lib/neo4j-core/query.rb +23 -20
  7. data/lib/neo4j-core/query_clauses.rb +18 -32
  8. data/lib/neo4j-core/query_find_in_batches.rb +3 -1
  9. data/lib/neo4j-core/version.rb +1 -1
  10. data/lib/neo4j-embedded/cypher_response.rb +4 -0
  11. data/lib/neo4j-embedded/embedded_database.rb +3 -5
  12. data/lib/neo4j-embedded/embedded_node.rb +4 -4
  13. data/lib/neo4j-embedded/embedded_session.rb +21 -10
  14. data/lib/neo4j-embedded/embedded_transaction.rb +4 -10
  15. data/lib/neo4j-server/cypher_node.rb +5 -4
  16. data/lib/neo4j-server/cypher_relationship.rb +3 -3
  17. data/lib/neo4j-server/cypher_response.rb +4 -0
  18. data/lib/neo4j-server/cypher_session.rb +31 -22
  19. data/lib/neo4j-server/cypher_transaction.rb +23 -15
  20. data/lib/neo4j-server/resource.rb +3 -4
  21. data/lib/neo4j/core/cypher_session.rb +17 -9
  22. data/lib/neo4j/core/cypher_session/adaptors.rb +116 -33
  23. data/lib/neo4j/core/cypher_session/adaptors/bolt.rb +331 -0
  24. data/lib/neo4j/core/cypher_session/adaptors/bolt/chunk_writer_io.rb +76 -0
  25. data/lib/neo4j/core/cypher_session/adaptors/bolt/pack_stream.rb +288 -0
  26. data/lib/neo4j/core/cypher_session/adaptors/embedded.rb +60 -29
  27. data/lib/neo4j/core/cypher_session/adaptors/has_uri.rb +63 -0
  28. data/lib/neo4j/core/cypher_session/adaptors/http.rb +123 -119
  29. data/lib/neo4j/core/cypher_session/responses.rb +17 -2
  30. data/lib/neo4j/core/cypher_session/responses/bolt.rb +135 -0
  31. data/lib/neo4j/core/cypher_session/responses/embedded.rb +46 -11
  32. data/lib/neo4j/core/cypher_session/responses/http.rb +49 -40
  33. data/lib/neo4j/core/cypher_session/transactions.rb +33 -0
  34. data/lib/neo4j/core/cypher_session/transactions/bolt.rb +36 -0
  35. data/lib/neo4j/core/cypher_session/transactions/embedded.rb +32 -0
  36. data/lib/neo4j/core/cypher_session/transactions/http.rb +52 -0
  37. data/lib/neo4j/core/instrumentable.rb +2 -2
  38. data/lib/neo4j/core/label.rb +182 -0
  39. data/lib/neo4j/core/node.rb +8 -3
  40. data/lib/neo4j/core/relationship.rb +12 -4
  41. data/lib/neo4j/entity_equality.rb +1 -1
  42. data/lib/neo4j/session.rb +4 -5
  43. data/lib/neo4j/transaction.rb +108 -72
  44. data/neo4j-core.gemspec +6 -6
  45. 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 { |e| wrap_entity(e) }
30
- elsif entity.is_a?(Java::OrgNeo4jKernelImplCore::NodeProxy)
31
- wrap_node(entity)
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
- wrap_relationship(entity)
59
+ :relationship
34
60
  elsif entity.respond_to?(:path_entities)
35
- wrap_path(entity)
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)).wrap
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)).wrap
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).wrap
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, request_data)
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(row_data, rest_data)
29
- row_data.each_with_index.map do |row_datum, i|
30
- rest_datum = rest_data[i]
31
-
32
- if rest_datum.is_a?(Array)
33
- wrap_entity(row_datum, rest_datum)
34
- elsif (ident = identify_entity(rest_datum))
35
- send("wrap_#{ident}", rest_datum, row_datum)
36
- else
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
- if type == 'node'
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
- metadata_data = rest_datum[:metadata]
60
- ::Neo4j::Core::Node.new(id_from_rest_datum(rest_datum),
61
- metadata_data && metadata_data[:labels],
62
- rest_datum[:data]).wrap
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
- metadata_data = rest_datum[:metadata]
67
- ::Neo4j::Core::Relationship.new(id_from_rest_datum(rest_datum),
68
- metadata_data && metadata_data[:type],
69
- rest_datum[:data]).wrap
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
- nodes = rest_datum[:nodes].each_with_index.map do |url, i|
74
- Node.from_url(url, row_datum[2 * i])
75
- end
76
- relationships = rest_datum[:relationships].each_with_index.map do |url, i|
77
- Relationship.from_url(url, row_datum[(2 * i) + 1])
78
- end
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
- ::Neo4j::Core::Path.new(nodes, relationships, rest_datum[:directions]).wrap
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].split('/').last.to_i
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 = <<-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).include?(status = faraday_response.status)
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