boltless 1.0.0
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 +7 -0
- data/Gemfile +8 -0
- data/Guardfile +44 -0
- data/Makefile +138 -0
- data/Rakefile +26 -0
- data/docker-compose.yml +19 -0
- data/lib/boltless/configuration.rb +69 -0
- data/lib/boltless/errors/invalid_json_error.rb +9 -0
- data/lib/boltless/errors/request_error.rb +24 -0
- data/lib/boltless/errors/response_error.rb +30 -0
- data/lib/boltless/errors/transaction_begin_error.rb +9 -0
- data/lib/boltless/errors/transaction_in_bad_state_error.rb +11 -0
- data/lib/boltless/errors/transaction_not_found_error.rb +11 -0
- data/lib/boltless/errors/transaction_rollback_error.rb +26 -0
- data/lib/boltless/extensions/configuration_handling.rb +37 -0
- data/lib/boltless/extensions/connection_pool.rb +127 -0
- data/lib/boltless/extensions/operations.rb +175 -0
- data/lib/boltless/extensions/transactions.rb +301 -0
- data/lib/boltless/extensions/utilities.rb +187 -0
- data/lib/boltless/request.rb +386 -0
- data/lib/boltless/result.rb +98 -0
- data/lib/boltless/result_row.rb +90 -0
- data/lib/boltless/statement_collector.rb +40 -0
- data/lib/boltless/transaction.rb +234 -0
- data/lib/boltless/version.rb +23 -0
- data/lib/boltless.rb +36 -0
- data/spec/benchmark/transfer.rb +57 -0
- data/spec/boltless/extensions/configuration_handling_spec.rb +39 -0
- data/spec/boltless/extensions/connection_pool_spec.rb +131 -0
- data/spec/boltless/extensions/operations_spec.rb +189 -0
- data/spec/boltless/extensions/transactions_spec.rb +418 -0
- data/spec/boltless/extensions/utilities_spec.rb +546 -0
- data/spec/boltless/request_spec.rb +946 -0
- data/spec/boltless/result_row_spec.rb +161 -0
- data/spec/boltless/result_spec.rb +127 -0
- data/spec/boltless/statement_collector_spec.rb +45 -0
- data/spec/boltless/transaction_spec.rb +601 -0
- data/spec/boltless_spec.rb +11 -0
- data/spec/fixtures/files/raw_result.yml +21 -0
- data/spec/fixtures/files/raw_result_with_graph_result.yml +48 -0
- data/spec/fixtures/files/raw_result_with_meta.yml +11 -0
- data/spec/fixtures/files/raw_result_with_stats.yml +26 -0
- data/spec/spec_helper.rb +89 -0
- metadata +384 -0
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Boltless::Extensions::Operations do
|
6
|
+
let(:described_class) { Boltless }
|
7
|
+
|
8
|
+
before { clean_neo4j! }
|
9
|
+
|
10
|
+
describe '.clear_database!' do
|
11
|
+
let(:action) { described_class.clear_database! }
|
12
|
+
let(:logger) { Logger.new(log_dev) }
|
13
|
+
let(:log_dev) { StringIO.new }
|
14
|
+
let(:log) { log_dev.string }
|
15
|
+
|
16
|
+
before do
|
17
|
+
described_class.configuration.logger = logger
|
18
|
+
described_class.add_index(name: 'user_id', for: '(n:User)', on: 'n.id')
|
19
|
+
described_class.add_constraint(name: 'uniq_user_email', for: '(n:User)',
|
20
|
+
require: 'n.email IS UNIQUE')
|
21
|
+
described_class.one_shot! do |tx|
|
22
|
+
tx.add('CREATE (n:User { name: $name })', name: 'Klaus')
|
23
|
+
tx.add('CREATE (n:User { name: $name })', name: 'Bernd')
|
24
|
+
tx.add('MATCH (a:User { name: $a_name }) ' \
|
25
|
+
'MATCH (b:User { name: $b_name }) ' \
|
26
|
+
'CREATE (a)-[:FRIEND_OF { since: $since }]->(b)',
|
27
|
+
a_name: 'Klaus', b_name: 'Bernd', since: Date.today.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'removes all indexes' do
|
32
|
+
expect { action }.to \
|
33
|
+
change { described_class.index_names.count }.from(1).to(0)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'removes all constraints' do
|
37
|
+
expect { action }.to \
|
38
|
+
change { described_class.constraint_names.count }.from(1).to(0)
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'removes all nodes' do
|
42
|
+
expect { action }.to \
|
43
|
+
change { described_class.query!('MATCH (n) RETURN count(n)').value }
|
44
|
+
.from(2).to(0)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'removes all relationships' do
|
48
|
+
check = proc do
|
49
|
+
described_class.query!('MATCH (a)-[r]->(b) RETURN type(r) AS type')
|
50
|
+
.count
|
51
|
+
end
|
52
|
+
expect { action }.to change(&check).from(1).to(0)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'logs the removed indexes' do
|
56
|
+
action
|
57
|
+
expect(log).to include('Drop neo4j index user_id')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'logs the removed constraints' do
|
61
|
+
action
|
62
|
+
expect(log).to include('Drop neo4j constraint uniq_user_email')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'logs the removed nodes count' do
|
66
|
+
action
|
67
|
+
expect(log).to include('Nodes deleted: 2')
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'logs the removed relationships count' do
|
71
|
+
action
|
72
|
+
expect(log).to include('Relationships deleted: 1')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '.component_name_present?' do
|
77
|
+
before do
|
78
|
+
described_class.add_index(name: 'user_id', for: '(n:User)', on: 'n.id')
|
79
|
+
described_class.add_constraint(name: 'uniq_user_email', for: '(n:User)',
|
80
|
+
require: 'n.email IS UNIQUE')
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'with an known index' do
|
84
|
+
it 'returns true' do
|
85
|
+
expect(described_class.component_name_present?('user_id')).to \
|
86
|
+
be_eql(true)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'with an known constraint' do
|
91
|
+
it 'returns true' do
|
92
|
+
expect(described_class.component_name_present?('uniq_user_email')).to \
|
93
|
+
be_eql(true)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'with an unknown name' do
|
98
|
+
it 'returns false' do
|
99
|
+
expect(described_class.component_name_present?('unknown')).to \
|
100
|
+
be_eql(false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '.index_names' do
|
106
|
+
before do
|
107
|
+
described_class.add_index(name: 'user_id', for: '(n:User)', on: 'n.id')
|
108
|
+
described_class.add_index(name: 'session_id', for: '(n:Session)',
|
109
|
+
on: 'n.id')
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns the known index names' do
|
113
|
+
expect(described_class.index_names).to \
|
114
|
+
match_array(%w[user_id session_id])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '.constraint_names' do
|
119
|
+
before do
|
120
|
+
described_class.add_constraint(name: 'uniq_user_email',
|
121
|
+
for: '(n:User)',
|
122
|
+
require: 'n.email IS UNIQUE')
|
123
|
+
described_class.add_constraint(name: 'uniq_user_session',
|
124
|
+
for: '(n:Session)',
|
125
|
+
require: 'n.user_id IS UNIQUE')
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'returns the known constraint names' do
|
129
|
+
expect(described_class.constraint_names).to \
|
130
|
+
match_array(%w[uniq_user_email uniq_user_session])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '.add_index' do
|
135
|
+
it 'allows to create a new index' do
|
136
|
+
described_class.add_index(name: 'user_id', for: '(n:User)', on: 'n.id')
|
137
|
+
expect(described_class.index_names).to match_array(['user_id'])
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '.drop_index' do
|
142
|
+
before do
|
143
|
+
described_class.add_index(name: 'user_id', for: '(n:User)', on: 'n.id')
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'with an existing index' do
|
147
|
+
it 'allows to drop an index' do
|
148
|
+
described_class.drop_index('user_id')
|
149
|
+
expect(described_class.index_names).to match_array([])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'without an existing index' do
|
154
|
+
it 'does not raise errors' do
|
155
|
+
expect { described_class.drop_index('unknown') }.not_to raise_error
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe '.add_constraint' do
|
161
|
+
it 'allows to create a new constraint' do
|
162
|
+
described_class.add_constraint(name: 'uniq_user_email', for: '(n:User)',
|
163
|
+
require: 'n.email IS UNIQUE')
|
164
|
+
expect(described_class.constraint_names).to \
|
165
|
+
match_array(['uniq_user_email'])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '.drop_constraint' do
|
170
|
+
before do
|
171
|
+
described_class.add_constraint(name: 'uniq_user_email', for: '(n:User)',
|
172
|
+
require: 'n.email IS UNIQUE')
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'with an existing constraint' do
|
176
|
+
it 'allows to drop an constraint' do
|
177
|
+
described_class.drop_constraint('uniq_user_email')
|
178
|
+
expect(described_class.constraint_names).to match_array([])
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'without an existing constraint' do
|
183
|
+
it 'does not raise errors' do
|
184
|
+
expect { described_class.drop_constraint('unknown') }.not_to \
|
185
|
+
raise_error
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
@@ -0,0 +1,418 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Boltless::Extensions::Transactions do
|
6
|
+
let(:described_class) { Boltless }
|
7
|
+
let(:access_mode) { :write }
|
8
|
+
let(:statement_payload) do
|
9
|
+
cypher, params = statement
|
10
|
+
[cypher, (params || {}).merge(opts)]
|
11
|
+
end
|
12
|
+
let(:statement_payloads) do
|
13
|
+
statements.map do |(cypher, params)|
|
14
|
+
[cypher, (params || {}).merge(opts)]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
let(:opts) { {} }
|
18
|
+
let(:create_users) do
|
19
|
+
Boltless.one_shot! do |tx|
|
20
|
+
%w[Bernd Klaus Uwe Monika].each do |name|
|
21
|
+
tx.add('CREATE (n:User { name: $name })', name: name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
let(:create_user_statement) do
|
26
|
+
['CREATE (n:User { name: $name })', { name: 'Silke' }]
|
27
|
+
end
|
28
|
+
let(:fetch_users_statement) do
|
29
|
+
['MATCH (n:User) RETURN n.name AS name', {}]
|
30
|
+
end
|
31
|
+
let(:count_users_statement) do
|
32
|
+
['MATCH (n:User) RETURN count(n) AS count', {}]
|
33
|
+
end
|
34
|
+
let(:statement_with_syntax_errors) do
|
35
|
+
['SOME THING!', {}]
|
36
|
+
end
|
37
|
+
let(:fetch_date_statement) do
|
38
|
+
['RETURN date() AS date', {}]
|
39
|
+
end
|
40
|
+
let(:fetch_static_number_statement) do
|
41
|
+
['RETURN 9867 AS number', {}]
|
42
|
+
end
|
43
|
+
|
44
|
+
before { clean_neo4j! }
|
45
|
+
|
46
|
+
describe '.execute!' do
|
47
|
+
let(:action) do
|
48
|
+
cypher, args = statement_payload
|
49
|
+
described_class.execute!(cypher, **args)
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'with Cypher syntax errors' do
|
53
|
+
let(:statement) { statement_with_syntax_errors }
|
54
|
+
|
55
|
+
it 'raises an Boltless::Errors::TransactionRollbackError' do
|
56
|
+
expect { action }.to \
|
57
|
+
raise_error(Boltless::Errors::TransactionRollbackError,
|
58
|
+
/invalid input/i)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'with multiple rows' do
|
63
|
+
let(:statement) { fetch_users_statement }
|
64
|
+
|
65
|
+
before { create_users }
|
66
|
+
|
67
|
+
it 'returns the user names' do
|
68
|
+
expect(action.pluck(:name)).to \
|
69
|
+
match_array(%w[Bernd Klaus Uwe Monika])
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns a Boltless::Result' do
|
73
|
+
expect(action).to be_a(Boltless::Result)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'with write operations on a read-only transaction' do
|
78
|
+
let(:statement) { create_user_statement }
|
79
|
+
let(:opts) { { access_mode: :read } }
|
80
|
+
|
81
|
+
it 'raises an Boltless::Errors::TransactionRollbackError' do
|
82
|
+
expect { action }.to \
|
83
|
+
raise_error(Boltless::Errors::TransactionRollbackError,
|
84
|
+
/Neo.ClientError.Request.Invalid/i)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '.execute' do
|
90
|
+
let(:action) do
|
91
|
+
cypher, args = statement_payload
|
92
|
+
described_class.execute(cypher, **args)
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with Cypher syntax errors' do
|
96
|
+
let(:statement) { statement_with_syntax_errors }
|
97
|
+
|
98
|
+
it 'returns nil' do
|
99
|
+
expect(action).to be(nil)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'with multiple rows' do
|
104
|
+
let(:statement) { fetch_users_statement }
|
105
|
+
|
106
|
+
before { create_users }
|
107
|
+
|
108
|
+
it 'returns the user names' do
|
109
|
+
expect(action.pluck(:name)).to \
|
110
|
+
match_array(%w[Bernd Klaus Uwe Monika])
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'returns a Boltless::Result' do
|
114
|
+
expect(action).to be_a(Boltless::Result)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
context 'with write operations on a read-only transaction' do
|
119
|
+
let(:statement) { create_user_statement }
|
120
|
+
let(:opts) { { access_mode: :read } }
|
121
|
+
|
122
|
+
it 'returns nil' do
|
123
|
+
expect(action).to be(nil)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe '.one_shot!' do
|
129
|
+
let(:action) do
|
130
|
+
described_class.one_shot!(access_mode) do |tx|
|
131
|
+
statement_payloads.each { |cypher, args| tx.add(cypher, **args) }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'with an error in between' do
|
136
|
+
let(:statements) do
|
137
|
+
[
|
138
|
+
create_user_statement,
|
139
|
+
statement_with_syntax_errors,
|
140
|
+
create_user_statement,
|
141
|
+
fetch_users_statement
|
142
|
+
]
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'raises an Boltless::Errors::TransactionRollbackError' do
|
146
|
+
expect { action }.to \
|
147
|
+
raise_error(Boltless::Errors::TransactionRollbackError,
|
148
|
+
/Neo.ClientError.Statement.SyntaxError/i)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'rolls back the transaction (no data is written)' do
|
152
|
+
suppress(StandardError) { action }
|
153
|
+
cypher, args = count_users_statement
|
154
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(0)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'with multiple statements' do
|
159
|
+
let(:statements) do
|
160
|
+
[
|
161
|
+
create_user_statement,
|
162
|
+
create_user_statement,
|
163
|
+
create_user_statement,
|
164
|
+
count_users_statement
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'returns 4 results (one for each statement)' do
|
169
|
+
expect(action.count).to be_eql(4)
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'returns the correct created user count' do
|
173
|
+
expect(action.last.value).to be_eql(3)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe '.one_shot' do
|
179
|
+
let(:action) do
|
180
|
+
described_class.one_shot(access_mode) do |tx|
|
181
|
+
statement_payloads.each { |cypher, args| tx.add(cypher, **args) }
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'with an error in between' do
|
186
|
+
let(:statements) do
|
187
|
+
[
|
188
|
+
create_user_statement,
|
189
|
+
statement_with_syntax_errors,
|
190
|
+
create_user_statement,
|
191
|
+
fetch_users_statement
|
192
|
+
]
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'returns nil' do
|
196
|
+
expect(action).to be(nil)
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'rolls back the transaction (no data is written)' do
|
200
|
+
suppress(StandardError) { action }
|
201
|
+
cypher, args = count_users_statement
|
202
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(0)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
context 'with multiple statements' do
|
207
|
+
let(:statements) do
|
208
|
+
[
|
209
|
+
create_user_statement,
|
210
|
+
create_user_statement,
|
211
|
+
create_user_statement,
|
212
|
+
count_users_statement
|
213
|
+
]
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'returns 4 results (one for each statement)' do
|
217
|
+
expect(action.count).to be_eql(4)
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'returns the correct created user count' do
|
221
|
+
expect(action.last.value).to be_eql(3)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '.transaction!' do
|
227
|
+
let(:action) { described_class.transaction!(access_mode, &user_block) }
|
228
|
+
|
229
|
+
context 'with an error in between' do
|
230
|
+
let(:user_block) do
|
231
|
+
proc do |tx|
|
232
|
+
cypher, args = create_user_statement
|
233
|
+
tx.run!(cypher, **args)
|
234
|
+
|
235
|
+
cypher, args = statement_with_syntax_errors
|
236
|
+
tx.run!(cypher, **args)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'raises an Boltless::Errors::TransactionRollbackError' do
|
241
|
+
expect { action }.to \
|
242
|
+
raise_error(Boltless::Errors::TransactionRollbackError,
|
243
|
+
/Neo.ClientError.Statement.SyntaxError/i)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'rolls back the transaction (no data is written)' do
|
247
|
+
suppress(StandardError) { action }
|
248
|
+
cypher, args = count_users_statement
|
249
|
+
expect(described_class.execute!(cypher, **args).value).to \
|
250
|
+
be_eql(0)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'with manual rollback (without raised errors)' do
|
255
|
+
let(:user_block) do
|
256
|
+
proc do |tx|
|
257
|
+
cypher, args = create_user_statement
|
258
|
+
tx.run!(cypher, **args)
|
259
|
+
tx.rollback!
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'returns true' do
|
264
|
+
expect(action).to be_eql(true)
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'rolls back the transaction (no data is written)' do
|
268
|
+
suppress(StandardError) { action }
|
269
|
+
cypher, args = count_users_statement
|
270
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(0)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
context 'with manual commit (without raised errors)' do
|
275
|
+
let(:user_block) do
|
276
|
+
proc do |tx|
|
277
|
+
cypher, args = create_user_statement
|
278
|
+
tx.run!(cypher, **args)
|
279
|
+
tx.commit!
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'returns an empty array (due to no finalization statements given)' do
|
284
|
+
expect(action).to match_array([])
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'completed the transaction (data is written)' do
|
288
|
+
suppress(StandardError) { action }
|
289
|
+
cypher, args = count_users_statement
|
290
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(1)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'with intermediate results' do
|
295
|
+
# rubocop:disable RSpec/MultipleExpectations because of the
|
296
|
+
# in-block testing
|
297
|
+
# rubocop:disable RSpec/ExampleLength dito
|
298
|
+
it 'allows direct access to each result' do
|
299
|
+
Boltless.transaction! do |tx|
|
300
|
+
cypher, args = fetch_date_statement
|
301
|
+
expect(tx.run!(cypher, **args).value).to be_eql(Date.today.to_s)
|
302
|
+
|
303
|
+
cypher, args = fetch_static_number_statement
|
304
|
+
expect(tx.run!(cypher, **args).value).to be_eql(9867)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
# rubocop:enable RSpec/MultipleExpectations
|
308
|
+
# rubocop:enable RSpec/ExampleLength
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe '.transaction' do
|
313
|
+
let(:action) { described_class.transaction(access_mode, &user_block) }
|
314
|
+
|
315
|
+
context 'with an error in between (not raised)' do
|
316
|
+
let(:user_block) do
|
317
|
+
proc do |tx|
|
318
|
+
cypher, args = create_user_statement
|
319
|
+
tx.run(cypher, **args)
|
320
|
+
|
321
|
+
cypher, args = statement_with_syntax_errors
|
322
|
+
tx.run(cypher, **args)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'returns nil' do
|
327
|
+
expect(action).to be(nil)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'rolls back the transaction (no data is written)' do
|
331
|
+
suppress(StandardError) { action }
|
332
|
+
cypher, args = count_users_statement
|
333
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(0)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
context 'with an error in between (raised)' do
|
338
|
+
let(:user_block) do
|
339
|
+
proc do |tx|
|
340
|
+
cypher, args = create_user_statement
|
341
|
+
tx.run(cypher, **args)
|
342
|
+
|
343
|
+
cypher, args = statement_with_syntax_errors
|
344
|
+
tx.run!(cypher, **args)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
it 'raises an Boltless::Errors::TransactionRollbackError' do
|
349
|
+
expect { action }.to \
|
350
|
+
raise_error(Boltless::Errors::TransactionRollbackError,
|
351
|
+
/Neo.ClientError.Statement.SyntaxError/i)
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'rolls back the transaction (no data is written)' do
|
355
|
+
suppress(StandardError) { action }
|
356
|
+
cypher, args = count_users_statement
|
357
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(0)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
context 'with manual rollback (without raised errors)' do
|
362
|
+
let(:user_block) do
|
363
|
+
proc do |tx|
|
364
|
+
cypher, args = create_user_statement
|
365
|
+
tx.run(cypher, **args)
|
366
|
+
tx.rollback
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'returns true' do
|
371
|
+
expect(action).to be_eql(true)
|
372
|
+
end
|
373
|
+
|
374
|
+
it 'rolls back the transaction (no data is written)' do
|
375
|
+
suppress(StandardError) { action }
|
376
|
+
cypher, args = count_users_statement
|
377
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(0)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
context 'with manual commit (without raised errors)' do
|
382
|
+
let(:user_block) do
|
383
|
+
proc do |tx|
|
384
|
+
cypher, args = create_user_statement
|
385
|
+
tx.run(cypher, **args)
|
386
|
+
tx.commit
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'returns an empty array (due to no finalization statements given)' do
|
391
|
+
expect(action).to match_array([])
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'completed the transaction (data is written)' do
|
395
|
+
suppress(StandardError) { action }
|
396
|
+
cypher, args = count_users_statement
|
397
|
+
expect(described_class.execute!(cypher, **args).value).to be_eql(1)
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
context 'with intermediate results' do
|
402
|
+
# rubocop:disable RSpec/MultipleExpectations because of the
|
403
|
+
# in-block testing
|
404
|
+
# rubocop:disable RSpec/ExampleLength dito
|
405
|
+
it 'allows direct access to each result' do
|
406
|
+
Boltless.transaction do |tx|
|
407
|
+
cypher, args = fetch_date_statement
|
408
|
+
expect(tx.run(cypher, **args).value).to be_eql(Date.today.to_s)
|
409
|
+
|
410
|
+
cypher, args = fetch_static_number_statement
|
411
|
+
expect(tx.run(cypher, **args).value).to be_eql(9867)
|
412
|
+
end
|
413
|
+
end
|
414
|
+
# rubocop:enable RSpec/MultipleExpectations
|
415
|
+
# rubocop:enable RSpec/ExampleLength
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|