neo4j-core 5.1.14 → 6.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/neo4j-core.rb +1 -1
  3. data/lib/neo4j-core/helpers.rb +6 -0
  4. data/lib/neo4j-core/query.rb +88 -40
  5. data/lib/neo4j-core/query_clauses.rb +67 -67
  6. data/lib/neo4j-core/version.rb +1 -1
  7. data/lib/neo4j-embedded/embedded_label.rb +16 -14
  8. data/lib/neo4j-embedded/embedded_node.rb +115 -113
  9. data/lib/neo4j-embedded/embedded_relationship.rb +53 -51
  10. data/lib/neo4j-embedded/embedded_session.rb +5 -14
  11. data/lib/neo4j-embedded/label.rb +2 -1
  12. data/lib/neo4j-server/cypher_label.rb +8 -7
  13. data/lib/neo4j-server/cypher_relationship.rb +1 -1
  14. data/lib/neo4j-server/cypher_response.rb +3 -2
  15. data/lib/neo4j-server/cypher_transaction.rb +1 -6
  16. data/lib/neo4j-server/resource.rb +1 -1
  17. data/lib/neo4j/core/cypher_session.rb +28 -0
  18. data/lib/neo4j/core/cypher_session/adaptors.rb +108 -0
  19. data/lib/neo4j/core/cypher_session/adaptors/embedded.rb +81 -0
  20. data/lib/neo4j/core/cypher_session/adaptors/http.rb +159 -0
  21. data/lib/neo4j/core/cypher_session/responses.rb +27 -0
  22. data/lib/neo4j/core/cypher_session/responses/embedded.rb +77 -0
  23. data/lib/neo4j/core/cypher_session/responses/http.rb +104 -0
  24. data/lib/neo4j/core/cypher_session/result.rb +39 -0
  25. data/lib/neo4j/core/instrumentable.rb +36 -0
  26. data/lib/neo4j/core/node.rb +28 -0
  27. data/lib/neo4j/core/path.rb +15 -0
  28. data/lib/neo4j/core/relationship.rb +25 -0
  29. data/lib/neo4j/core/wrappable.rb +35 -0
  30. data/lib/neo4j/label.rb +7 -0
  31. data/lib/neo4j/session.rb +3 -3
  32. data/lib/neo4j/transaction.rb +1 -24
  33. data/neo4j-core.gemspec +2 -2
  34. metadata +22 -9
@@ -26,13 +26,9 @@ module Neo4j
26
26
  def properties_map
27
27
  return @properties_map if @properties_map
28
28
 
29
- props = if @config[:properties_map].is_a?(Hash)
30
- @config[:properties_map].each_with_object({}) do |(k, v), m|
31
- m[k.to_s.to_java] = v.to_s.to_java
32
- end
33
- else
34
- {}
35
- end
29
+ props = @config[:properties_map].each_with_object({}) do |(k, v), m|
30
+ m[k.to_s.to_java] = v.to_s.to_java
31
+ end
36
32
  @properties_map = java.util.HashMap.new(props)
37
33
  end
38
34
 
@@ -46,6 +42,7 @@ module Neo4j
46
42
 
47
43
  def version
48
44
  # Wow
45
+ # Yeah, agreed...
49
46
  version_string = @graph_db.to_java(Java::OrgNeo4jKernel::GraphDatabaseAPI).getDependencyResolver.resolveDependency(Java::OrgNeo4jKernel::KernelData.java_class).version.to_s
50
47
  version_string.split(' ')[-1]
51
48
  end
@@ -150,18 +147,12 @@ module Neo4j
150
147
  ActiveSupport::Notifications.instrument('neo4j.cypher_query', params: params, context: options[:context],
151
148
  cypher: query, pretty_cypher: options[:pretty_cypher], params: params) do
152
149
  @engine ||= Java::OrgNeo4jCypherJavacompat::ExecutionEngine.new(@graph_db)
153
- @engine.execute(query, indifferent_params(params))
150
+ @engine.execute(query, HashWithIndifferentAccess.new(params))
154
151
  end
155
152
  rescue StandardError => e
156
153
  raise Neo4j::Session::CypherError.new(e.message, e.class, 'cypher error')
157
154
  end
158
155
 
159
- def indifferent_params(params)
160
- return {} unless params
161
- params.each { |k, v| params[k] = HashWithIndifferentAccess.new(params[k]) if v.is_a?(Hash) && !v.respond_to?(:nested_under_indifferent_access) }
162
- HashWithIndifferentAccess.new(params)
163
- end
164
-
165
156
  def query_default_return(as)
166
157
  " RETURN #{as}"
167
158
  end
@@ -22,7 +22,8 @@ module Neo4j
22
22
  schema.get_indexes.to_a.each do |i|
23
23
  begin
24
24
  i.drop
25
- rescue Java::JavaLang::IllegalStateException; end
25
+ rescue Java::JavaLang::IllegalStateException
26
+ end
26
27
  end
27
28
  end
28
29
  end
@@ -10,16 +10,17 @@ module Neo4j
10
10
  @session = session
11
11
  end
12
12
 
13
- def create_index(*properties)
14
- response = @session._query("CREATE INDEX ON :`#{@name}`(#{properties.join(',')})")
13
+ def create_index(property, options = {}, session = Neo4j::Session.current)
14
+ validate_index_options!(options)
15
+ properties = property.is_a?(Array) ? property.join(',') : property
16
+ response = session._query("CREATE INDEX ON :`#{@name}`(#{properties})")
15
17
  response.raise_error if response.error?
16
18
  end
17
19
 
18
- def drop_index(*properties)
19
- properties.each do |property|
20
- response = @session._query("DROP INDEX ON :`#{@name}`(#{property})")
21
- response.raise_error if response.error? && !response.error_msg.match(/No such INDEX ON/)
22
- end
20
+ def drop_index(property, options = {}, session = Neo4j::Session.current)
21
+ validate_index_options!(options)
22
+ response = session._query("DROP INDEX ON :`#{@name}`(#{property})")
23
+ response.raise_error if response.error? && !response.error_msg.match(/No such INDEX ON/)
23
24
  end
24
25
 
25
26
  def indexes
@@ -85,7 +85,7 @@ module Neo4j
85
85
 
86
86
  # (see Neo4j::Relationship#props=)
87
87
  def props=(properties)
88
- @session._query_or_fail("#{match_start} SET n = { props }", false, props: properties, neo_id: neo_id)
88
+ @session._query_or_fail("#{match_start} SET n = { props }", false, props: properties, neo_id: neo_id)
89
89
  properties
90
90
  end
91
91
 
@@ -101,9 +101,10 @@ module Neo4j
101
101
  def identify_entity(data)
102
102
  self_string = data[:self]
103
103
  if self_string
104
- if self_string.include?('node')
104
+ type = self_string.split('/')[-2]
105
+ if type == 'node'
105
106
  :node
106
- elsif self_string.include?('relationship')
107
+ elsif type == 'relationship'
107
108
  :relationship
108
109
  end
109
110
  elsif [:nodes, :relationships, :start, :end, :length].all? { |k| data.key?(k) }
@@ -73,17 +73,12 @@ module Neo4j
73
73
  cypher_response.set_data(first_result)
74
74
  else
75
75
  first_error = response.body[:errors].first
76
- tx_cleanup!(first_error)
76
+ mark_expired if first_error[:message].match(/Unrecognized transaction id/)
77
77
  cypher_response.set_error(first_error)
78
78
  end
79
79
  end
80
80
  end
81
81
 
82
- def tx_cleanup!(first_error)
83
- autoclosed!
84
- mark_expired if first_error[:message].match(/Unrecognized transaction id/)
85
- end
86
-
87
82
  def empty_response
88
83
  OpenStruct.new(status: 200, body: '')
89
84
  end
@@ -47,7 +47,7 @@ module Neo4j
47
47
  end
48
48
 
49
49
  def resource_url_id(url = resource_url)
50
- url.match(/\/(\d+)$/)[1].to_i
50
+ url.match(%r{/(\d+)$})[1].to_i
51
51
  end
52
52
 
53
53
  def convert_from_json_value(value)
@@ -0,0 +1,28 @@
1
+ require 'neo4j/core/cypher_session/adaptors/http'
2
+
3
+ module Neo4j
4
+ module Core
5
+ class CypherSession
6
+ def initialize(adaptor)
7
+ fail ArgumentError, "Invalid adaptor: #{adaptor.inspect}" if !adaptor.is_a?(Adaptors::Base)
8
+
9
+ @adaptor = adaptor
10
+
11
+ @adaptor.connect
12
+ end
13
+
14
+ %w(
15
+ query
16
+ queries
17
+ start_transaction
18
+ end_transaction
19
+ version
20
+ transactions
21
+ ).each do |method|
22
+ define_method(method) do |*args|
23
+ @adaptor.send(method, *args)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,108 @@
1
+ require 'neo4j/core/instrumentable'
2
+
3
+ module Neo4j
4
+ module Core
5
+ class CypherSession
6
+ module Adaptors
7
+ MAP = {}
8
+
9
+ class Base
10
+ include Neo4j::Core::Instrumentable
11
+
12
+ def connect(*_args)
13
+ fail '#connect not implemented!'
14
+ end
15
+
16
+ Query = Struct.new(:cypher, :parameters, :pretty_cypher, :context)
17
+
18
+ class QueryBuilder
19
+ attr_reader :queries
20
+
21
+ def initialize
22
+ @queries = []
23
+ end
24
+
25
+ def append(*args)
26
+ query = case args.map(&:class)
27
+ when [String], [String, Hash]
28
+ Query.new(args[0], args[1] || {})
29
+ when [::Neo4j::Core::Query]
30
+ args[0]
31
+ else
32
+ fail ArgumentError, "Could not determine query from arguments: #{args.inspect}"
33
+ end
34
+
35
+ @queries << query
36
+ end
37
+
38
+ def query
39
+ # `nil` sessions are just a workaround until
40
+ # we phase out `Query` objects containing sessions
41
+ Neo4j::Core::Query.new(session: nil)
42
+ end
43
+ end
44
+
45
+ def query(cypher, parameters = {})
46
+ queries do
47
+ append(cypher, parameters)
48
+ end[0]
49
+ end
50
+
51
+ def queries(&block)
52
+ query_builder = QueryBuilder.new
53
+
54
+ query_builder.instance_eval(&block)
55
+
56
+ query_set(query_builder.queries)
57
+ end
58
+
59
+ def query_set(_queries)
60
+ fail '#queries not implemented!'
61
+ end
62
+
63
+ def start_transaction(*_args)
64
+ fail '#start_transaction not implemented!'
65
+ end
66
+
67
+ def end_transaction(*_args)
68
+ fail '#end_transaction not implemented!'
69
+ end
70
+
71
+ def version(*_args)
72
+ fail '#version not implemented!'
73
+ end
74
+
75
+ # Uses #start_transaction and #end_transaction to allow
76
+ # execution of queries within a block to be part of a
77
+ # full transaction
78
+ def transaction
79
+ start_transaction
80
+
81
+ yield
82
+ ensure
83
+ end_transaction
84
+ end
85
+
86
+ EMPTY = ''
87
+ NEWLINE_W_SPACES = "\n "
88
+
89
+ instrument(:query, 'neo4j.core.cypher_query', %w(query)) do |_, _start, _finish, _id, payload|
90
+ query = payload[:query]
91
+ params_string = (query.parameters && query.parameters.size > 0 ? "| #{query.parameters.inspect}" : EMPTY)
92
+ cypher = query.pretty_cypher ? NEWLINE_W_SPACES + query.pretty_cypher.gsub(/\n/, NEWLINE_W_SPACES) : query.cypher
93
+
94
+ " #{ANSI::CYAN}#{query.context || 'CYPHER'}#{ANSI::CLEAR} #{cypher} #{params_string}"
95
+ end
96
+
97
+ class << self
98
+ def instrument_queries(queries)
99
+ queries.each do |query|
100
+ instrument_query(query) {}
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,81 @@
1
+ require 'neo4j/core/cypher_session/adaptors'
2
+ require 'neo4j/core/cypher_session/responses/embedded'
3
+
4
+ module Neo4j
5
+ module Core
6
+ class CypherSession
7
+ module Adaptors
8
+ class Embedded < Base
9
+ def initialize(path, options = {})
10
+ fail 'JRuby is required for embedded mode' if RUBY_PLATFORM != 'java'
11
+ fail ArgumentError, "Invalid path: #{path}" if !File.directory?(path)
12
+
13
+ @path = path
14
+ @options = options
15
+ end
16
+
17
+ def connect
18
+ factory = Java::OrgNeo4jGraphdbFactory::GraphDatabaseFactory.new
19
+ db_service = factory.newEmbeddedDatabaseBuilder(@path)
20
+ db_service.loadPropertiesFromFile(@options[:properties_file]) if @options[:properties_file]
21
+ db_service.setConfig(@options[:properties_map]) if @options[:properties_map]
22
+
23
+ @graph_db = db_service.newGraphDatabase
24
+ end
25
+
26
+ def query_set(queries)
27
+ # I think that this is the best way to do a batch in embedded...
28
+ # Should probably do within a transaction in case of errors...
29
+
30
+ transaction do
31
+ self.class.instrument_transaction do
32
+ self.class.instrument_queries(queries)
33
+
34
+ execution_results = queries.map do |query|
35
+ engine.execute(query.cypher, HashWithIndifferentAccess.new(query.parameters))
36
+ end
37
+
38
+ Responses::Embedded.new(execution_results).results
39
+ end
40
+ end
41
+ end
42
+
43
+ def start_transaction
44
+ @transaction = @graph_db.begin_tx
45
+ end
46
+
47
+ def end_transaction
48
+ if @transaction.nil?
49
+ fail 'Cannot close transaction without starting one'
50
+ end
51
+
52
+ @transaction.success
53
+ @transaction.close
54
+ end
55
+
56
+ def version
57
+ if defined?(::Neo4j::Community)
58
+ ::Neo4j::Community::NEO_VERSION
59
+ elsif defined?(::Neo4j::Enterprise)
60
+ ::Neo4j::Enterprise::NEO_VERSION
61
+ else
62
+ fail 'Could not determine embedded version!'
63
+ end
64
+ end
65
+
66
+ instrument(:transaction, 'neo4j.core.embedded.transaction', []) do |_, start, finish, _id, _payload|
67
+ ms = (finish - start) * 1000
68
+
69
+ " #{ANSI::BLUE}EMBEDDED CYPHER TRANSACTION:#{ANSI::CLEAR} #{ANSI::YELLOW}#{ms.round}ms#{ANSI::CLEAR}"
70
+ end
71
+
72
+ private
73
+
74
+ def engine
75
+ @engine ||= Java::OrgNeo4jCypherJavacompat::ExecutionEngine.new(@graph_db)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,159 @@
1
+ require 'neo4j/core/cypher_session/adaptors'
2
+ require 'neo4j/core/cypher_session/responses/http'
3
+
4
+ # TODO: Work with `Query` objects
5
+ module Neo4j
6
+ module Core
7
+ class CypherSession
8
+ module Adaptors
9
+ class HTTP < Base
10
+ # @transaction_state valid states
11
+ # nil, :open_requested, :open, :close_requested
12
+
13
+ def initialize(url, _options = {})
14
+ @url = url
15
+ @url_components = url_components!(url)
16
+ @transaction_state = nil
17
+ end
18
+
19
+ def connect
20
+ @connection ||= connection
21
+ end
22
+
23
+ ROW_REST = %w(row REST)
24
+
25
+ def query_set(queries)
26
+ fail 'Query attempted without a connection' if @connection.nil?
27
+
28
+ statements_data = queries.map do |query|
29
+ {statement: query.cypher, parameters: query.parameters || {},
30
+ resultDataContents: ROW_REST}
31
+ end
32
+ request_data = {statements: statements_data}
33
+
34
+ # context option not implemented
35
+ self.class.instrument_queries(queries)
36
+
37
+ url = full_transaction_url
38
+ faraday_response = self.class.instrument_request(url, request_data) do
39
+ @connection.post(url, request_data)
40
+ end
41
+
42
+ store_transaction_id!(faraday_response)
43
+
44
+ Responses::HTTP.new(faraday_response, request_data).results
45
+ end
46
+
47
+ def start_transaction
48
+ case @transaction_state
49
+ when :open
50
+ return
51
+ when :close_requested
52
+ fail 'Cannot start transaction when a close has been requested'
53
+ end
54
+
55
+ @transaction_state = :open_requested
56
+ end
57
+
58
+ def end_transaction
59
+ if @transaction_state.nil?
60
+ fail 'Cannot close transaction without starting one'
61
+ end
62
+
63
+ # This needs thought through more...
64
+ @transaction_state = :close_requested
65
+ query_set([])
66
+ @transaction_state = nil
67
+
68
+ true
69
+ end
70
+
71
+ def version
72
+ @version ||= @connection.get(db_data_url).body[:neo4j_version]
73
+ end
74
+
75
+ instrument(:request, 'neo4j.core.http.request', %w(url body)) do |_, start, finish, _id, payload|
76
+ ms = (finish - start) * 1000
77
+
78
+ " #{ANSI::BLUE}HTTP REQUEST:#{ANSI::CLEAR} #{ANSI::YELLOW}#{ms.round}ms#{ANSI::CLEAR} #{payload[:url]}"
79
+ end
80
+
81
+ private
82
+
83
+ def store_transaction_id!(faraday_response)
84
+ location = faraday_response.env[:response_headers][:location]
85
+
86
+ return if !location
87
+
88
+ @transaction_id = location.split('/').last.to_i
89
+ end
90
+
91
+ def full_transaction_url
92
+ path = case @transaction_state
93
+ when nil then '/commit'
94
+ when :open_requested then ''
95
+ when :open then "/#{@transaction_id}"
96
+ when :close_requested then "/#{@transaction_id}/commit"
97
+ end
98
+
99
+ db_data_url + 'transaction' + path
100
+ end
101
+
102
+ def db_data_url
103
+ url_base + 'db/data/'
104
+ end
105
+
106
+ def url_base
107
+ "#{scheme}://#{host}:#{port}/"
108
+ end
109
+
110
+ def connection
111
+ Faraday.new(@url) do |c|
112
+ c.request :basic_auth, user, password
113
+ c.request :multi_json
114
+
115
+ c.response :multi_json, symbolize_keys: true, content_type: 'application/json'
116
+ c.use Faraday::Adapter::NetHttpPersistent
117
+
118
+ c.headers['Content-Type'] = 'application/json'
119
+ c.headers['User-Agent'] = user_agent_string
120
+ end
121
+ end
122
+
123
+ def url_components!(url)
124
+ @uri = URI(url || 'http://localhost:7474')
125
+
126
+ if !@uri.is_a?(URI::HTTP)
127
+ fail ArgumentError, "Invalid URL: #{url.inspect}"
128
+ end
129
+
130
+ true
131
+ end
132
+
133
+ URI_DEFAULTS = {
134
+ scheme: 'http',
135
+ user: 'neo4j', password: 'neo4j',
136
+ host: 'localhost', port: 7474
137
+ }
138
+
139
+ URI_DEFAULTS.each do |method, value|
140
+ define_method(method) do
141
+ @uri.send(method) || value
142
+ end
143
+ end
144
+
145
+ def user_agent_string
146
+ gem, version = if defined?(::Neo4j::ActiveNode)
147
+ ['neo4j', ::Neo4j::VERSION]
148
+ else
149
+ ['neo4j-core', ::Neo4j::Core::VERSION]
150
+ end
151
+
152
+
153
+ "#{gem}-gem/#{version} (https://github.com/neo4jrb/#{gem})"
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end