neo4j-core 5.1.7 → 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/cypher_session/adaptors/embedded.rb +81 -0
- data/lib/neo4j/core/cypher_session/adaptors/http.rb +159 -0
- data/lib/neo4j/core/cypher_session/adaptors.rb +108 -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/responses.rb +27 -0
- data/lib/neo4j/core/cypher_session/result.rb +39 -0
- data/lib/neo4j/core/cypher_session.rb +28 -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-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-core.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 +1 -0
- 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/resource.rb +1 -1
- data/neo4j-core.gemspec +2 -2
- metadata +22 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d2e549ac9b629bd47019215c2457d66c6b355a2f
|
|
4
|
+
data.tar.gz: 717fb2cc5363c0a7a9726ae4d686e504634d7310
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 14df3ab1fb20692a66036201039f66861b38fc340b3c63c7051d977b2a718c09a9daa3b5d717920ddaa5228089c899f8cf9f611c2e11e6cda992af6fc6d1838a
|
|
7
|
+
data.tar.gz: ece61afe7b0c8c21db72322bfe5143ccc9ab7ef84dc6b01f7b7416b7978b28a082b0edb7dbc5587ed390cc9aa2da3555f4aad1fa11e96fb28e76e5de9d90e7c4
|
|
@@ -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
|
|
@@ -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,77 @@
|
|
|
1
|
+
require 'neo4j/core/cypher_session/responses'
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Core
|
|
5
|
+
class CypherSession
|
|
6
|
+
module Responses
|
|
7
|
+
class Embedded < Base
|
|
8
|
+
attr_reader :results, :request_data
|
|
9
|
+
|
|
10
|
+
def initialize(execution_results)
|
|
11
|
+
@results = execution_results.map do |execution_result|
|
|
12
|
+
result_from_execution_result(execution_result)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def result_from_execution_result(execution_result)
|
|
19
|
+
columns = execution_result.columns.to_a
|
|
20
|
+
rows = execution_result.map do |execution_result_row|
|
|
21
|
+
columns.map { |column| wrap_entity(execution_result_row[column]) }
|
|
22
|
+
end
|
|
23
|
+
Result.new(columns, rows)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def wrap_entity(entity)
|
|
27
|
+
if entity.is_a?(Array) ||
|
|
28
|
+
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)
|
|
32
|
+
elsif entity.is_a?(Java::OrgNeo4jKernelImplCore::RelationshipProxy)
|
|
33
|
+
wrap_relationship(entity)
|
|
34
|
+
elsif entity.respond_to?(:path_entities)
|
|
35
|
+
wrap_path(entity)
|
|
36
|
+
else
|
|
37
|
+
wrap_value(entity)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def wrap_node(entity)
|
|
42
|
+
::Neo4j::Core::Node.new(entity.get_id,
|
|
43
|
+
entity.get_labels.map(&:to_s),
|
|
44
|
+
get_entity_properties(entity)).wrap
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def wrap_relationship(entity)
|
|
48
|
+
::Neo4j::Core::Relationship.new(entity.get_id,
|
|
49
|
+
entity.get_type.name,
|
|
50
|
+
get_entity_properties(entity)).wrap
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def wrap_path(entity)
|
|
54
|
+
::Neo4j::Core::Path.new(entity.nodes.map(&method(:wrap_node)),
|
|
55
|
+
entity.relationships.map(&method(:wrap_relationship)),
|
|
56
|
+
nil).wrap
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def wrap_value(entity)
|
|
60
|
+
if entity.is_a?(Java::ScalaCollectionConvert::Wrappers::MapWrapper)
|
|
61
|
+
entity.each_with_object({}) { |(k, v), r| r[k.to_sym] = v }
|
|
62
|
+
else
|
|
63
|
+
# Convert from Java?
|
|
64
|
+
entity
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def get_entity_properties(entity)
|
|
69
|
+
entity.get_property_keys.each_with_object({}) do |key, result|
|
|
70
|
+
result[key.to_sym] = entity.get_property(key)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
require 'neo4j/core/cypher_session/responses'
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Core
|
|
5
|
+
class CypherSession
|
|
6
|
+
module Responses
|
|
7
|
+
class HTTP < Base
|
|
8
|
+
attr_reader :results, :request_data
|
|
9
|
+
|
|
10
|
+
def initialize(faraday_response, request_data)
|
|
11
|
+
@request_data = request_data
|
|
12
|
+
|
|
13
|
+
validate_faraday_response!(faraday_response)
|
|
14
|
+
|
|
15
|
+
@results = faraday_response.body[:results].map do |result_data|
|
|
16
|
+
result_from_data(result_data[:columns], result_data[:data])
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def result_from_data(columns, entities_data)
|
|
21
|
+
rows = entities_data.map do |entity_data|
|
|
22
|
+
wrap_entity entity_data[:row], entity_data[:rest]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Result.new(columns, rows)
|
|
26
|
+
end
|
|
27
|
+
|
|
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
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def identify_entity(rest_datum)
|
|
45
|
+
self_string = rest_datum[:self]
|
|
46
|
+
if self_string
|
|
47
|
+
type = self_string.split('/')[-2]
|
|
48
|
+
if type == 'node'
|
|
49
|
+
:node
|
|
50
|
+
elsif type == 'relationship'
|
|
51
|
+
:relationship
|
|
52
|
+
end
|
|
53
|
+
elsif [:nodes, :relationships, :start, :end, :length].all? { |k| rest_datum.key?(k) }
|
|
54
|
+
:path
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
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
|
|
63
|
+
end
|
|
64
|
+
|
|
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
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
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
|
|
79
|
+
|
|
80
|
+
::Neo4j::Core::Path.new(nodes, relationships, rest_datum[:directions]).wrap
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def id_from_rest_datum(rest_datum)
|
|
84
|
+
if rest_datum[:metadata]
|
|
85
|
+
rest_datum[:metadata][:id]
|
|
86
|
+
else
|
|
87
|
+
rest_datum[:self].split('/').last.to_i
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_faraday_response!(faraday_response)
|
|
92
|
+
if error = faraday_response.body[:errors][0]
|
|
93
|
+
fail CypherError, "#{ANSI::CYAN}#{error[:code]}#{ANSI::CLEAR}: #{error[:message]}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
return if (200..299).include?(status = faraday_response.status)
|
|
97
|
+
|
|
98
|
+
fail CypherError, "Expected 200-series response for #{faraday_response.env.url} (got #{status})"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'neo4j/core/cypher_session/result'
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Core
|
|
5
|
+
class CypherSession
|
|
6
|
+
module Responses
|
|
7
|
+
MAP = {}
|
|
8
|
+
|
|
9
|
+
class Base
|
|
10
|
+
class CypherError < StandardError; end
|
|
11
|
+
|
|
12
|
+
include Enumerable
|
|
13
|
+
|
|
14
|
+
def each
|
|
15
|
+
results.each do |result|
|
|
16
|
+
yield result
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def results
|
|
21
|
+
fail '#results not implemented!'
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'neo4j/core/node'
|
|
2
|
+
require 'neo4j/core/relationship'
|
|
3
|
+
require 'neo4j/core/path'
|
|
4
|
+
|
|
5
|
+
module Neo4j
|
|
6
|
+
module Core
|
|
7
|
+
class CypherSession
|
|
8
|
+
class Result
|
|
9
|
+
attr_reader :columns, :rows
|
|
10
|
+
|
|
11
|
+
def initialize(columns, rows)
|
|
12
|
+
@columns = columns.map(&:to_sym)
|
|
13
|
+
@rows = rows
|
|
14
|
+
@struct_class = Struct.new(*@columns)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
include Enumerable
|
|
18
|
+
|
|
19
|
+
def each
|
|
20
|
+
structs.each do |struct|
|
|
21
|
+
yield struct
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def structs
|
|
26
|
+
@structs ||= rows.map do |row|
|
|
27
|
+
@struct_class.new(*row)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def hashes
|
|
32
|
+
@hashes ||= rows.map do |row|
|
|
33
|
+
Hash[@columns.zip(row)]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
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,36 @@
|
|
|
1
|
+
require 'active_support/notifications'
|
|
2
|
+
|
|
3
|
+
module Neo4j
|
|
4
|
+
module Core
|
|
5
|
+
module Instrumentable
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.send :include, InstanceMethods
|
|
8
|
+
base.extend ClassMethods
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module InstanceMethods
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def instrument(name, label, arguments, &block)
|
|
16
|
+
# defining class methods
|
|
17
|
+
klass = class << self; self; end
|
|
18
|
+
klass.instance_eval do
|
|
19
|
+
define_method("subscribe_to_#{name}") do |&b|
|
|
20
|
+
ActiveSupport::Notifications.subscribe(label) do |a, start, finish, id, payload|
|
|
21
|
+
b.call block.call(a, start, finish, id, payload)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
define_method("instrument_#{name}") do |*args, &b|
|
|
26
|
+
hash = arguments.each_with_index.each_with_object({}) do |(argument, i), result|
|
|
27
|
+
result[argument.to_sym] = args[i]
|
|
28
|
+
end
|
|
29
|
+
ActiveSupport::Notifications.instrument(label, hash) { b.call }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|