neo4j-core 5.1.14 → 6.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/lib/neo4j-core.rb +1 -1
- data/lib/neo4j-core/helpers.rb +6 -0
- data/lib/neo4j-core/query.rb +88 -40
- data/lib/neo4j-core/query_clauses.rb +67 -67
- data/lib/neo4j-core/version.rb +1 -1
- data/lib/neo4j-embedded/embedded_label.rb +16 -14
- data/lib/neo4j-embedded/embedded_node.rb +115 -113
- data/lib/neo4j-embedded/embedded_relationship.rb +53 -51
- data/lib/neo4j-embedded/embedded_session.rb +5 -14
- data/lib/neo4j-embedded/label.rb +2 -1
- data/lib/neo4j-server/cypher_label.rb +8 -7
- data/lib/neo4j-server/cypher_relationship.rb +1 -1
- data/lib/neo4j-server/cypher_response.rb +3 -2
- data/lib/neo4j-server/cypher_transaction.rb +1 -6
- data/lib/neo4j-server/resource.rb +1 -1
- data/lib/neo4j/core/cypher_session.rb +28 -0
- data/lib/neo4j/core/cypher_session/adaptors.rb +108 -0
- data/lib/neo4j/core/cypher_session/adaptors/embedded.rb +81 -0
- data/lib/neo4j/core/cypher_session/adaptors/http.rb +159 -0
- data/lib/neo4j/core/cypher_session/responses.rb +27 -0
- data/lib/neo4j/core/cypher_session/responses/embedded.rb +77 -0
- data/lib/neo4j/core/cypher_session/responses/http.rb +104 -0
- data/lib/neo4j/core/cypher_session/result.rb +39 -0
- data/lib/neo4j/core/instrumentable.rb +36 -0
- data/lib/neo4j/core/node.rb +28 -0
- data/lib/neo4j/core/path.rb +15 -0
- data/lib/neo4j/core/relationship.rb +25 -0
- data/lib/neo4j/core/wrappable.rb +35 -0
- data/lib/neo4j/label.rb +7 -0
- data/lib/neo4j/session.rb +3 -3
- data/lib/neo4j/transaction.rb +1 -24
- data/neo4j-core.gemspec +2 -2
- 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 =
|
30
|
-
|
31
|
-
|
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,
|
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
|
data/lib/neo4j-embedded/label.rb
CHANGED
@@ -10,16 +10,17 @@ module Neo4j
|
|
10
10
|
@session = session
|
11
11
|
end
|
12
12
|
|
13
|
-
def create_index(
|
14
|
-
|
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(
|
19
|
-
|
20
|
-
|
21
|
-
|
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,
|
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
|
-
|
104
|
+
type = self_string.split('/')[-2]
|
105
|
+
if type == 'node'
|
105
106
|
:node
|
106
|
-
elsif
|
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
|
-
|
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
|
@@ -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
|