neo4j-core 3.0.0.alpha.18 → 3.0.0.alpha.19

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.
@@ -0,0 +1,155 @@
1
+ module Neo4j::Server
2
+ class CypherResponse
3
+ attr_reader :data, :columns, :error_msg, :error_status, :error_code, :response
4
+
5
+ class ResponseError < StandardError
6
+ attr_reader :status, :code
7
+
8
+ def initialize(msg, status, code)
9
+ super(msg)
10
+ @status = status
11
+ @code = code
12
+ end
13
+ end
14
+
15
+
16
+ class HashEnumeration
17
+ include Enumerable
18
+ extend Forwardable
19
+ def_delegator :@response, :error_msg
20
+ def_delegator :@response, :error_status
21
+ def_delegator :@response, :error_code
22
+ def_delegator :@response, :data
23
+ def_delegator :@response, :columns
24
+
25
+ def initialize(response, map_return_procs, query)
26
+ @response = response
27
+ @map_return_procs = map_return_procs
28
+ @query = query
29
+ end
30
+
31
+ def to_s
32
+ @query
33
+ end
34
+
35
+ puts "RELOADED"
36
+ def inspect
37
+ #"Enumerable query: '#{@query}' map_return: [#{@map_return_procs.keys.join(', ')}]"
38
+ "Enumerable query: '#{@query}' map_return: [#{@map_return_procs.keys.inspect}]"
39
+ end
40
+
41
+ def each_no_mapping
42
+ data.each do |row|
43
+ hash = {}
44
+ row.each_with_index do |row, i|
45
+ key = columns[i].to_sym
46
+ hash[key] = row
47
+ end
48
+ yield hash
49
+ end
50
+ end
51
+
52
+ def each_multi_column_mapping
53
+ data.each do |row|
54
+ hash = {}
55
+ row.each_with_index do |row, i|
56
+ key = columns[i].to_sym
57
+ proc = @map_return_procs[key]
58
+ hash[key] = proc ? proc.call(row) : row
59
+ end
60
+ yield hash
61
+ end
62
+ end
63
+
64
+ def each_single_column_mapping
65
+ data.each do |row|
66
+ result = @map_return_procs.call(row.first)
67
+ yield result
68
+ end
69
+ end
70
+
71
+ def each(&block)
72
+ case @map_return_procs
73
+ when NilClass then each_no_mapping &block
74
+ when Hash then each_multi_column_mapping &block
75
+ else each_single_column_mapping &block
76
+ end
77
+ end
78
+ end
79
+
80
+ def to_hash_enumeration(map_return_procs={}, cypher='')
81
+ HashEnumeration.new(self, map_return_procs, cypher)
82
+ end
83
+
84
+ def initialize(response, uncommited = false)
85
+ @response = response
86
+ @uncommited = uncommited
87
+ end
88
+
89
+
90
+ def first_data
91
+ if uncommited?
92
+ @data.first['row'].first
93
+ else
94
+ @data[0][0]
95
+ end
96
+ end
97
+
98
+ def error?
99
+ !!@error
100
+ end
101
+
102
+ def uncommited?
103
+ @uncommited
104
+ end
105
+
106
+ def raise_unless_response_code(code)
107
+ raise "Response code #{response.code}, expected #{code} for #{response.request.path}, #{response.body}" unless response.code == code
108
+ end
109
+
110
+ def set_data(data, columns)
111
+ @data = data
112
+ @columns = columns
113
+ self
114
+ end
115
+
116
+ def set_error(error_msg, error_status, error_core)
117
+ @error = true
118
+ @error_msg = error_msg
119
+ @error_status = error_status
120
+ @error_code = error_core
121
+ self
122
+ end
123
+
124
+ def raise_error
125
+ raise "Tried to raise error without an error" unless @error
126
+ raise ResponseError.new(@error_msg, @error_status, @error_code)
127
+ end
128
+
129
+ def self.create_with_no_tx(response)
130
+ case response.code
131
+ when 200
132
+ CypherResponse.new(response).set_data(response['data'], response['columns'])
133
+ when 400
134
+ CypherResponse.new(response).set_error(response['message'], response['exception'], response['fullname'])
135
+ else
136
+ raise "Unknown response code #{response.code} for #{response.request.path.to_s}"
137
+ end
138
+ end
139
+
140
+ def self.create_with_tx(response)
141
+ raise "Unknown response code #{response.code} for #{response.request.path.to_s}" unless response.code == 200
142
+
143
+ first_result = response['results'][0]
144
+ cr = CypherResponse.new(response, true)
145
+
146
+ if (response['errors'].empty?)
147
+ cr.set_data(first_result['data'], first_result['columns'])
148
+ else
149
+ first_error = response['errors'].first
150
+ cr.set_error(first_error['message'], first_error['status'], first_error['code'])
151
+ end
152
+ cr
153
+ end
154
+ end
155
+ end
@@ -37,6 +37,10 @@ module Neo4j::Server
37
37
  Neo4j::Session._notify_listeners(:session_available, self)
38
38
  end
39
39
 
40
+ def db_type
41
+ :server_db
42
+ end
43
+
40
44
  def to_s
41
45
  "#{self.class} url: '#{@resource_url}'"
42
46
  end
@@ -64,7 +68,13 @@ module Neo4j::Server
64
68
  end
65
69
 
66
70
  def begin_tx
67
- Thread.current[:neo4j_curr_tx] = wrap_resource(self, 'transaction', CypherTransaction, nil, :post, @endpoint)
71
+ if Neo4j::Transaction.current
72
+ # Handle nested transaction "placebo transaction"
73
+ Neo4j::Transaction.current.push_nested!
74
+ else
75
+ wrap_resource(self, 'transaction', CypherTransaction, nil, :post, @endpoint)
76
+ end
77
+ Neo4j::Transaction.current
68
78
  end
69
79
 
70
80
  def create_node(props=nil, labels=[])
@@ -77,8 +87,9 @@ module Neo4j::Server
77
87
  def load_node(neo_id)
78
88
  cypher_response = _query("START n=node(#{neo_id}) RETURN n")
79
89
  if (!cypher_response.error?)
80
- CypherNode.new(self, neo_id)
81
- elsif (cypher_response.error_status == 'EntityNotFoundException')
90
+ result = cypher_response.entity_data(neo_id)
91
+ CypherNode.new(self, result)
92
+ elsif (cypher_response.error_status =~ /EntityNotFound/)
82
93
  return nil
83
94
  else
84
95
  cypher_response.raise_error
@@ -140,12 +151,24 @@ module Neo4j::Server
140
151
  end
141
152
  end
142
153
 
154
+ def _query_data(q)
155
+ r = _query_or_fail(q, true)
156
+ # the response is different if we have a transaction or not
157
+ Neo4j::Transaction.current ? r : r['data']
158
+ end
159
+
143
160
  def _query_or_fail(q, single_row = false, params=nil)
144
161
  response = _query(q, params)
145
162
  response.raise_error if response.error?
146
163
  single_row ? response.first_data : response
147
164
  end
148
165
 
166
+ def _query_entity_data(q, id=nil)
167
+ response = _query(q)
168
+ response.raise_error if response.error?
169
+ response.entity_data(id)
170
+ end
171
+
149
172
  def _query(q, params=nil)
150
173
  curr_tx = Neo4j::Transaction.current
151
174
  if (curr_tx)
@@ -1,9 +1,10 @@
1
1
  module Neo4j::Server
2
2
  class CypherTransaction
3
- attr_reader :commit_url, :exec_url
4
-
5
- include Resource
3
+ include Neo4j::Transaction::Instance
6
4
  include Neo4j::Core::CypherTranslator
5
+ include Resource
6
+
7
+ attr_reader :commit_url, :exec_url
7
8
 
8
9
  class CypherError < StandardError
9
10
  attr_reader :code, :status
@@ -20,7 +21,7 @@ module Neo4j::Server
20
21
  @exec_url = response.headers['location']
21
22
  init_resource_data(response, url)
22
23
  expect_response_code(response,201)
23
- Neo4j::Transaction.register(self)
24
+ register_instance
24
25
  end
25
26
 
26
27
  def _query(cypher_query, params=nil)
@@ -41,7 +42,10 @@ module Neo4j::Server
41
42
  end
42
43
  end
43
44
  response = @endpoint.post(@exec_url, headers: resource_headers, body: body.to_json)
45
+ _create_cypher_response(response)
46
+ end
44
47
 
48
+ def _create_cypher_response(response)
45
49
  first_result = response['results'][0]
46
50
  cr = CypherResponse.new(response, true)
47
51
 
@@ -54,29 +58,18 @@ module Neo4j::Server
54
58
  cr
55
59
  end
56
60
 
57
- def success
58
- # this is need in the Java API
59
- end
60
61
 
61
- def failure
62
- @failure = true
63
- end
64
62
 
65
- def failure?
66
- !!@failure
63
+ def _delete_tx
64
+ response = @endpoint.delete(@exec_url, headers: resource_headers)
65
+ expect_response_code(response,200)
66
+ response
67
67
  end
68
68
 
69
- def finish
70
- Neo4j::Transaction.unregister(self)
71
- if failure?
72
- response = @endpoint.delete(@exec_url, headers: resource_headers)
73
- else
74
- response = @endpoint.post(@commit_url, headers: resource_headers)
75
- end
69
+ def _commit_tx
70
+ response = @endpoint.post(@commit_url, headers: resource_headers)
76
71
  expect_response_code(response,200)
77
72
  response
78
73
  end
79
-
80
-
81
74
  end
82
75
  end
data/lib/neo4j/node.rb CHANGED
@@ -31,6 +31,11 @@ module Neo4j
31
31
  raise 'not implemented'
32
32
  end
33
33
 
34
+ # Refresh the properties by reading it from the database again next time an property value is requested.
35
+ def refresh
36
+ raise 'not implemented'
37
+ end
38
+
34
39
  # Updates the properties, keeps old properties
35
40
  # @param [Hash<Symbol, Object>] properties hash of properties that should be updated on the node
36
41
  def update_props(properties)
@@ -191,12 +196,6 @@ module Neo4j
191
196
  session.load_node(neo_id)
192
197
  end
193
198
 
194
- # Checks if the given entity node or entity id (Neo4j::Node#neo_id) exists in the database.
195
- # @return [true, false] if exist
196
- def exist?(entity_or_entity_id, session = Neo4j::Session.current!)
197
- session.node_exist?(neo_id)
198
- end
199
-
200
199
  # Find the node with given label and value
201
200
  def find_nodes(label, value=nil, session = Neo4j::Session.current!)
202
201
  session.find_nodes(label, value)
@@ -205,7 +204,6 @@ module Neo4j
205
204
 
206
205
  def initialize
207
206
  raise "Can't instantiate abstract class" if abstract_class?
208
- puts "Instantiated!"
209
207
  end
210
208
 
211
209
  private
@@ -152,6 +152,11 @@ module Neo4j
152
152
  end
153
153
 
154
154
  def load(neo_id, session = Neo4j::Session.current)
155
+ rel = _load(neo_id, session)
156
+ rel && rel.wrapper
157
+ end
158
+
159
+ def _load(neo_id, session = Neo4j::Session.current)
155
160
  session.load_relationship(neo_id)
156
161
  end
157
162
 
data/lib/neo4j/session.rb CHANGED
@@ -28,6 +28,11 @@ module Neo4j
28
28
  raise "not impl."
29
29
  end
30
30
 
31
+ # @return [:embedded_db | :server_db]
32
+ def db_type
33
+ raise "not impl."
34
+ end
35
+
31
36
  def auto_commit?
32
37
  true # TODO
33
38
  end
@@ -106,6 +111,17 @@ module Neo4j
106
111
  @@current_session = session
107
112
  end
108
113
 
114
+ # Registers a callback which will be called immediately if session is already available,
115
+ # or called when it later becomes available.
116
+ def on_session_available(&callback)
117
+ if (Neo4j::Session.current)
118
+ callback.call(Neo4j::Session.current)
119
+ end
120
+ add_listener do |event, data|
121
+ callback.call(data) if event == :session_available
122
+ end
123
+ end
124
+
109
125
  def add_listener(&listener)
110
126
  self._listeners << listener
111
127
  end
@@ -0,0 +1,202 @@
1
+ module Neo4j
2
+ class Session
3
+
4
+ @@current_session = nil
5
+ @@all_sessions = {}
6
+ @@factories = {}
7
+
8
+ # @abstract
9
+ def close
10
+ self.class.unregister(self)
11
+ end
12
+
13
+ # Only for embedded database
14
+ # @abstract
15
+ def start
16
+ raise "not impl."
17
+ end
18
+
19
+ # Only for embedded database
20
+ # @abstract
21
+ def shutdown
22
+ raise "not impl."
23
+ end
24
+
25
+ # Only for embedded database
26
+ # @abstract
27
+ def running
28
+ raise "not impl."
29
+ end
30
+
31
+ def auto_commit?
32
+ true # TODO
33
+ end
34
+
35
+ # @abstract
36
+ def begin_tx
37
+ raise "not impl."
38
+ end
39
+
40
+ class CypherError < StandardError
41
+ attr_reader :error_msg, :error_status, :error_code
42
+ def initialize(error_msg, error_code, error_status)
43
+ super(error_msg)
44
+ @error_msg = error_msg
45
+ @error_status = error_status
46
+ end
47
+ end
48
+
49
+ # Executes a Cypher Query.
50
+ # Returns an enumerable of hash values where each hash corresponds to a row unless +return+ or +map_return+
51
+ # is not an array. The search result can be mapped to Neo4j::Node or Neo4j::Relationship is your own Ruby wrapper class
52
+ # by specifying a map_return parameter.
53
+ #
54
+ # @param [Hash, String] q the cypher query, as a pure string query or a hash which will generate a cypher string.
55
+ # @option q [Hash] :params cypher parameters
56
+ # @option q [Symbol,Hash] :label the label to match. You can specify several labels by using a hash of variable names and labels.
57
+ # @option q [Symbol] :conditions key and value of properties which the label nodes must match
58
+ # @option q [Hash] :conditions key and value of properties which the label nodes must match
59
+ # @option q [String, Array] :match the cypher match clause
60
+ # @option q [String, Array] :where the cypher where clause
61
+ # @option q [String, Array, Symbol] :return the cypher where clause
62
+ # @option q [String, Hash, Symbol] :map_return mapping of the returned values, e.g. :id_to_node, :id_to_rel, or :value
63
+ # @option q [Hash<Symbol, Proc>] :map_return_procs custom mapping functions of :map_return types
64
+ # @option q [String,Symbol,Array<Hash>] :order the order
65
+ # @option q [Fixnum] :limit enables the return of only subsets of the total result.
66
+ # @option q [Fixnum] :skip enables the return of only subsets of the total result.
67
+ # @return [Enumerable] the result, an enumerable of Neo4j::Node objects unless a pure cypher string is given or return/map_returns is specified, see examples.
68
+ # @raise CypherError if invalid cypher
69
+ # @example Cypher String and parameters
70
+ # Neo4j::Session.query("START n=node({p}) RETURN ID(n)", params: {p: 42})
71
+ #
72
+ # @example label
73
+ # # If there is no :return parameter it will try to return Neo4j::Node objects
74
+ # # Default parameter is :n in the generated cypher
75
+ # Neo4j::Session.query(label: :person) # => MATCH (n:`person`) RETURN ID(n) # or RETURN n for embedded
76
+ #
77
+ # @example to_s
78
+ # # What Cypher is returned ? check with to_s
79
+ # Neo4j::Session.query(label: :person).to_s # =>
80
+ #
81
+ # @example return
82
+ # Neo4j::Session.query(label: :person, return: :age) # returns age properties
83
+ # Neo4j::Session.query(label: :person, return: [:name, :age]) # returns a hash of name and age properties
84
+ # Neo4j::Session.query(label: :person, return: 'count(n) AS c')
85
+ #
86
+ # @example map_return - an Enumerable of names (String)
87
+ # Neo4j::Session.query("START n=node(42) RETURN n.name", map_return: :value)
88
+ #
89
+ # @example map_return - Enumerable of an Hash with name property, Neo4j::Relationship and Neo4j::Node as values
90
+ # Neo4j::Session.query("START n=node(42) MATCH n-[r]->[x] RETURN n.name as N, ID(r) as R, ID(x) as X",
91
+ # map_return: {N: :value, R: :id_to_rel, X: :id_to_node})
92
+ #
93
+ # @example map_return, only for embedded_db, to_rel, and to_node allows direct mapping to Neo4j::Node and Neo4j::Relationship without ID(n)
94
+ # Neo4j::Session.query("START n=node(42) MATCH n-[r]->[x] RETURN n.name as N, r, x", map_return: {N: :value, r: :to_rel, x: :to_node})
95
+ #
96
+ # @example map_return_procs, custom mapping function
97
+ # Neo4j::Session.query(label: :person, map_return: :age_times_two, map_return_procs: {age_times_two: ->(row){(row[:age] || 0) * 2}})
98
+ #
99
+ # @example match
100
+ # Neo4j::Session.query(label: :person, match: 'n--m')
101
+ #
102
+ # @example where
103
+ # Neo4j::Session.query(label: :person, where: 'n.age > 40')
104
+ # Neo4j::Session.query(label: :person, where: 'n.age > {age}', params: {age: 40})
105
+ #
106
+ # @example condition
107
+ # Neo4j::Session.query(label: :person, conditions: {age: 42})
108
+ # Neo4j::Session.query(label: :person, conditions: {name: /foo?bar.*/})
109
+ #
110
+ # @see http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html The Cypher Query Language Documentation
111
+ # @note Returns a read-once only forward iterable for the embedded database.
112
+ #
113
+ def query(q)
114
+ raise 'not implemented, abstract'
115
+ end
116
+
117
+ # Same as #query but does not accept an DSL and returns the raw result from the database.
118
+ # Notice, it might return different values depending on which database is used, embedded or server.
119
+ # @abstract
120
+ def _query(*params)
121
+ raise 'not implemented'
122
+ end
123
+
124
+ class << self
125
+ # Creates a new session to Neo4j
126
+ # @see also Neo4j::Server::CypherSession#open for :server_db params
127
+ # @param db_type the type of database, e.g. :embedded_db, or :server_db
128
+ def open(db_type=:server_db, *params)
129
+ register(create_session(db_type, params))
130
+ end
131
+
132
+ def open_named(db_type, name, default = nil, *params)
133
+ raise "Multiple sessions is currently only supported for Neo4j Server connections." unless db_type == :server_db
134
+ register(create_session(db_type, params), name, default)
135
+ end
136
+
137
+ def create_session(db_type, params = {})
138
+ unless (@@factories[db_type])
139
+ raise "Can't connect to database '#{db_type}', available #{@@factories.keys.join(',')}"
140
+ end
141
+ @@factories[db_type].call(*params)
142
+ end
143
+
144
+ def current
145
+ @@current_session
146
+ end
147
+
148
+ # @see Neo4j::Session#query
149
+ def query(*params)
150
+ current.query(*params)
151
+ end
152
+
153
+ def named(name)
154
+ @@all_sessions[name] || raise("No session named #{name}.")
155
+ end
156
+
157
+ def set_current(session)
158
+ @@current_session = session
159
+ end
160
+
161
+ def add_listener(&listener)
162
+ self._listeners << listener
163
+ end
164
+
165
+ def _listeners
166
+ @@listeners ||= []
167
+ @@listeners
168
+ end
169
+
170
+ def _notify_listeners(event, data)
171
+ _listeners.each {|li| li.call(event, data)}
172
+ end
173
+
174
+ def register(session, name = nil, default = nil)
175
+ if default == true
176
+ set_current(session)
177
+ elsif default.nil?
178
+ set_current(session) unless @@current_session
179
+ end
180
+ @@all_sessions[name] = session if name
181
+ @@current_session
182
+ end
183
+
184
+ def unregister(session)
185
+ @@current_session = nil if @@current_session == session
186
+ end
187
+
188
+ def all_sessions
189
+ @@all_sessions
190
+ end
191
+
192
+ def inspect
193
+ "Neo4j::Session available: #{@@factories && @@factories.keys}"
194
+ end
195
+
196
+ def register_db(db, &session_factory)
197
+ puts "replace factory for #{db}" if @@factories[db]
198
+ @@factories[db] = session_factory
199
+ end
200
+ end
201
+ end
202
+ end