Skip to content

Commit aa69345

Browse files
committed
Merge branch 'master' of github.com:neo4jrb/neo4j-core
2 parents 2902d2f + b70bee0 commit aa69345

11 files changed

+215
-514
lines changed

Gemfile

-5
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,7 @@ gem 'coveralls', require: false
1111
gem 'simplecov-html', require: false
1212

1313
group 'development' do
14-
gem 'pry-rescue', platform: :ruby
15-
gem 'pry-stack_explorer', platform: :ruby
16-
17-
gem 'guard'
1814
gem 'guard-rspec', require: false
19-
gem 'guard-rubocop'
2015
end
2116

2217
group 'test' do

lib/neo4j-server/cypher_relationship.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def rel_type
105105
end
106106

107107
def del
108-
@session._query("#{match_start} DELETE n", neo_id: neo_id).raise_unless_response_code(200)
108+
@session._query("#{match_start} DELETE n", neo_id: neo_id)
109109
end
110110
alias_method :delete, :del
111111
alias_method :destroy, :del

lib/neo4j-server/cypher_response.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def data?
130130
end
131131

132132
def raise_unless_response_code(code)
133-
fail "Response code #{response.code}, expected #{code} for #{response.request.path}, #{response.body}" unless response.status == code
133+
fail "Response code #{response.status}, expected #{code} for #{response.headers['location']}, #{response.body}" unless response.status == code
134134
end
135135

136136
def each_data_row

lib/neo4j-server/cypher_session.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def begin_tx
105105
# Handle nested transaction "placebo transaction"
106106
Neo4j::Transaction.current.push_nested!
107107
else
108-
wrap_resource('transaction', CypherTransaction, :post, @connection)
108+
wrap_resource(@connection)
109109
end
110110
Neo4j::Transaction.current
111111
end

lib/neo4j-server/cypher_transaction.rb

+49-33
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,78 @@
11
module Neo4j
22
module Server
3+
# The CypherTransaction object lifecycle is as follows:
4+
# * It is initialized with the transactional endpoint URL and the connection object to use for communication. It does not communicate with the server to create this.
5+
# * The first query within the transaction sets the commit and execution addresses, :commit_url and :exec_url.
6+
# * At any time, `failure` can be called to mark a transaction failed and trigger a rollback upon closure.
7+
# * `close` is called to end the transaction. It calls `_commit_tx` or `_delete_tx`.
8+
#
9+
# If a transaction is created and then closed without performing any queries, an OpenStruct is returned that behaves like a successfully closed query.
310
class CypherTransaction
411
include Neo4j::Transaction::Instance
512
include Neo4j::Core::CypherTranslator
613
include Resource
714

8-
attr_reader :commit_url, :exec_url
15+
attr_reader :commit_url, :exec_url, :base_url, :connection
916

10-
class CypherError < StandardError
11-
attr_reader :code, :status
12-
def initialize(code, status, message)
13-
super(message)
14-
@code = code
15-
@status = status
16-
end
17-
end
18-
19-
def initialize(response, url, connection)
20-
@connection = connection
21-
@commit_url = response.body['commit']
22-
@exec_url = response.headers['Location']
23-
fail "NO ENDPOINT URL #{@connection} : HEAD: #{response.headers.inspect}" if !@exec_url || @exec_url.empty?
24-
init_resource_data(response.body, url)
25-
expect_response_code(response, 201)
17+
def initialize(url, session_connection)
18+
@base_url = url
19+
@connection = session_connection
2620
register_instance
2721
end
2822

23+
ROW_REST = %w(row REST)
2924
def _query(cypher_query, params = nil)
30-
statement = {statement: cypher_query, parameters: params, resultDataContents: %w(row REST)}
25+
fail 'Transaction expired, unable to perform query' if expired?
26+
statement = {statement: cypher_query, parameters: params, resultDataContents: ROW_REST}
3127
body = {statements: [statement]}
32-
response = @connection.post(@exec_url, body)
28+
29+
response = exec_url && commit_url ? connection.post(exec_url, body) : register_urls(body)
3330
_create_cypher_response(response)
3431
end
3532

33+
def _delete_tx
34+
_tx_query(:delete, exec_url, headers: resource_headers)
35+
end
36+
37+
def _commit_tx
38+
_tx_query(:post, commit_url, nil)
39+
end
40+
41+
private
42+
43+
def _tx_query(action, endpoint, headers = {})
44+
return empty_response if !commit_url || expired?
45+
response = connection.send(action, endpoint, headers)
46+
expect_response_code(response, 200)
47+
response
48+
end
49+
50+
def register_urls(body)
51+
response = connection.post(base_url, body)
52+
@commit_url = response.body['commit']
53+
@exec_url = response.headers['Location']
54+
fail "NO ENDPOINT URL #{connection} : HEAD: #{response.headers.inspect}" if !exec_url || exec_url.empty?
55+
init_resource_data(response.body, base_url)
56+
expect_response_code(response, 201)
57+
response
58+
end
59+
3660
def _create_cypher_response(response)
3761
first_result = response.body['results'][0]
3862

3963
cr = CypherResponse.new(response, true)
40-
if !response.body['errors'].empty?
64+
if response.body['errors'].empty?
65+
cr.set_data(first_result['data'], first_result['columns'])
66+
else
4167
first_error = response.body['errors'].first
68+
expired if first_error['message'].match(/Unrecognized transaction id/)
4269
cr.set_error(first_error['message'], first_error['code'], first_error['code'])
43-
else
44-
cr.set_data(first_result['data'], first_result['columns'])
4570
end
4671
cr
4772
end
4873

49-
def _delete_tx
50-
response = @connection.delete(@exec_url, headers: resource_headers)
51-
expect_response_code(response, 200)
52-
response
53-
end
54-
55-
def _commit_tx
56-
response = @connection.post(@commit_url)
57-
58-
expect_response_code(response, 200)
59-
response
74+
def empty_response
75+
OpenStruct.new(status: 200, body: '')
6076
end
6177
end
6278
end

lib/neo4j-server/resource.rb

+3-9
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,9 @@ def init_resource_data(resource_data, resource_url)
1616
self
1717
end
1818

19-
20-
def wrap_resource(key, resource_class, verb = :get, statement = {}, connection)
21-
fail "Illegal verb #{verb}" if not [:get, :post].include?(verb)
22-
23-
url = resource_url(key)
24-
25-
response = connection.send(verb, url, statement.empty? ? nil : statement)
26-
27-
resource_class.new(response, url, connection) if response.status != 404
19+
def wrap_resource(connection = Neo4j::Session.current)
20+
url = resource_url('transaction')
21+
CypherTransaction.new(url, connection)
2822
end
2923

3024
def resource_url(key = nil)

lib/neo4j/transaction.rb

+17-12
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,26 @@ def register_instance
99
Neo4j::Transaction.register(self)
1010
end
1111

12-
# Marks this transaction as failed, which means that it will unconditionally be rolled back when close() is called.
13-
def failure
12+
# Marks this transaction as failed, which means that it will unconditionally be rolled back when close() is called. Aliased for legacy purposes.
13+
def mark_failed
1414
@failure = true
1515
end
16+
alias_method :failure, :mark_failed
1617

17-
# If it has been marked as failed
18-
def failure?
18+
# If it has been marked as failed. Aliased for legacy purposes.
19+
def failed?
1920
!!@failure
2021
end
22+
alias_method :failure?, :failed?
23+
24+
def mark_expired
25+
@expired = true
26+
end
27+
alias_method :expired, :mark_expired
28+
29+
def expired?
30+
!!@expired
31+
end
2132

2233
# @private
2334
def push_nested!
@@ -51,16 +62,10 @@ def close
5162
return if @pushed_nested >= 0
5263
fail "Can't commit transaction, already committed" if @pushed_nested < -1
5364
Neo4j::Transaction.unregister(self)
54-
if failure?
55-
_delete_tx
56-
else
57-
_commit_tx
58-
end
65+
failed? ? _delete_tx : _commit_tx
5966
end
6067
end
6168

62-
63-
6469
# @return [Neo4j::Transaction::Instance]
6570
def new(current = Session.current!)
6671
current.begin_tx
@@ -82,7 +87,7 @@ def run(run_in_tx = true)
8287
puts "Java Exception in a transaction, cause: #{e.cause}"
8388
e.cause.print_stack_trace
8489
end
85-
tx.failure unless tx.nil?
90+
tx.mark_failed unless tx.nil?
8691
raise
8792
ensure
8893
tx.close unless tx.nil?

spec/neo4j-server/e2e/cypher_transaction_spec.rb

+61-47
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,78 @@ module Server
1010
Neo4j::Transaction.current && Neo4j::Transaction.current.close
1111
end
1212

13-
it 'can open and commit a transaction' do
14-
tx = session.begin_tx
15-
tx.close
16-
end
17-
18-
it 'can run a valid query' do
19-
id = session.query.create('(n)').return('ID(n) AS id').first[:id]
13+
context 'where no queries are made' do
14+
it 'can open and close a transaction' do
15+
tx = session.begin_tx
16+
expect { tx.close }.not_to raise_error
17+
end
2018

21-
tx = session.begin_tx
22-
q = tx._query("MATCH (n) WHERE ID(n) = #{id} RETURN ID(n)")
23-
expect(q.response.body['results']).to eq([{'columns' => ['ID(n)'], 'data' => [{'row' => [id], 'rest' => [id]}]}])
19+
it 'returns an OpenStruct to mimic a completed transaction' do
20+
tx = session.begin_tx
21+
response = tx.close
22+
expect(response.status).to eq(200)
23+
expect(response).to be_a(OpenStruct)
24+
end
2425
end
2526

27+
context 'where queries are made' do
28+
it 'can open and close a transaction' do
29+
tx = session.begin_tx
30+
tx._query("CREATE (n:Student { name: 'John' } RETURN n")
31+
response = tx.close
32+
expect(response.status).to eq 200
33+
expect(response).to be_a(Faraday::Response)
34+
end
2635

27-
it 'sets the response error fields if not a valid query' do
28-
tx = session.begin_tx
29-
r = tx._query('START n=fs(0) RRETURN ID(n)')
30-
expect(r.error?).to be true
31-
32-
expect(r.error_msg).to match(/Invalid input/)
33-
expect(r.error_status).to match(/Syntax/)
34-
end
36+
it 'can run a valid query' do
37+
id = session.query.create('(n)').return('ID(n) AS id').first[:id]
38+
tx = session.begin_tx
39+
q = tx._query("MATCH (n) WHERE ID(n) = #{id} RETURN ID(n)")
40+
expect(q.response.body['results']).to eq([{'columns' => ['ID(n)'], 'data' => [{'row' => [id], 'rest' => [id]}]}])
41+
end
3542

36-
it 'can commit' do
37-
tx = session.begin_tx
38-
response = tx.close
39-
expect(response.status).to eq(200)
40-
end
43+
it 'sets the response error fields if not a valid query' do
44+
tx = session.begin_tx
45+
r = tx._query('START n=fs(0) RRETURN ID(n)')
46+
expect(r.error?).to be true
4147

42-
it 'can rollback' do
43-
node = Neo4j::Node.create(name: 'andreas')
44-
Neo4j::Transaction.run do |tx|
45-
node[:name] = 'foo'
46-
expect(node[:name]).to eq('foo')
47-
tx.failure
48+
expect(r.error_msg).to match(/Invalid input/)
49+
expect(r.error_status).to match(/Syntax/)
4850
end
4951

50-
expect(node['name']).to eq('andreas')
51-
end
52+
it 'can rollback' do
53+
node = Neo4j::Node.create(name: 'andreas')
54+
Neo4j::Transaction.run do |tx|
55+
node[:name] = 'foo'
56+
expect(node[:name]).to eq('foo')
57+
tx.mark_failed
58+
end
5259

53-
it 'can continue operations after transaction is rolled back' do
54-
node = Neo4j::Node.create(name: 'andreas')
55-
Neo4j::Transaction.run do |tx|
56-
tx.failure
57-
node[:name] = 'foo'
58-
expect(node[:name]).to eq('foo')
60+
expect(node['name']).to eq('andreas')
61+
end
62+
63+
it 'can continue operations after transaction is rolled back' do
64+
node = Neo4j::Node.create(name: 'andreas')
65+
Neo4j::Transaction.run do |tx|
66+
tx.mark_failed
67+
node[:name] = 'foo'
68+
expect(node[:name]).to eq('foo')
69+
end
70+
expect(node['name']).to eq('andreas')
5971
end
60-
expect(node['name']).to eq('andreas')
61-
end
6272

63-
it 'can use Transaction block style' do
64-
node = Neo4j::Transaction.run do
65-
Neo4j::Node.create(name: 'andreas')
73+
it 'cannot continue operations if a transaction is expired' do
74+
node = Neo4j::Node.create(name: 'andreas')
75+
Neo4j::Transaction.run do |tx|
76+
tx.expired
77+
expect { node[:name] = 'foo' }.to raise_error 'Transaction expired, unable to perform query'
78+
end
6679
end
6780

68-
expect(node['name']).to eq('andreas')
81+
it 'can use Transaction block style' do
82+
node = Neo4j::Transaction.run { Neo4j::Node.create(name: 'andreas') }
83+
expect(node['name']).to eq('andreas')
84+
end
6985
end
7086

7187
describe Neo4j::Label do
@@ -140,8 +156,8 @@ module Server
140156
loaded = a.rel(dir: :outgoing, type: :knows)
141157
expect(loaded).to eq(rel)
142158
expect(loaded['colour']).to eq('blue')
143-
ensure
144-
tx.close
159+
ensure
160+
tx.close
145161
end
146162
end
147163
end
@@ -158,7 +174,6 @@ module Server
158174

159175
describe '#del' do
160176
it 'deletes a node' do
161-
skip 'see https://github.com/neo4j/neo4j/issues/2943'
162177
begin
163178
tx = session.begin_tx
164179
node = Neo4j::Node.create(name: 'andreas')
@@ -172,7 +187,6 @@ module Server
172187
end
173188
end
174189

175-
176190
describe '#[]=' do
177191
it 'can update/read a property' do
178192
node = Neo4j::Node.create(name: 'foo')

0 commit comments

Comments
 (0)