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