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.
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