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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/Guardfile +44 -0
  4. data/Makefile +138 -0
  5. data/Rakefile +26 -0
  6. data/docker-compose.yml +19 -0
  7. data/lib/boltless/configuration.rb +69 -0
  8. data/lib/boltless/errors/invalid_json_error.rb +9 -0
  9. data/lib/boltless/errors/request_error.rb +24 -0
  10. data/lib/boltless/errors/response_error.rb +30 -0
  11. data/lib/boltless/errors/transaction_begin_error.rb +9 -0
  12. data/lib/boltless/errors/transaction_in_bad_state_error.rb +11 -0
  13. data/lib/boltless/errors/transaction_not_found_error.rb +11 -0
  14. data/lib/boltless/errors/transaction_rollback_error.rb +26 -0
  15. data/lib/boltless/extensions/configuration_handling.rb +37 -0
  16. data/lib/boltless/extensions/connection_pool.rb +127 -0
  17. data/lib/boltless/extensions/operations.rb +175 -0
  18. data/lib/boltless/extensions/transactions.rb +301 -0
  19. data/lib/boltless/extensions/utilities.rb +187 -0
  20. data/lib/boltless/request.rb +386 -0
  21. data/lib/boltless/result.rb +98 -0
  22. data/lib/boltless/result_row.rb +90 -0
  23. data/lib/boltless/statement_collector.rb +40 -0
  24. data/lib/boltless/transaction.rb +234 -0
  25. data/lib/boltless/version.rb +23 -0
  26. data/lib/boltless.rb +36 -0
  27. data/spec/benchmark/transfer.rb +57 -0
  28. data/spec/boltless/extensions/configuration_handling_spec.rb +39 -0
  29. data/spec/boltless/extensions/connection_pool_spec.rb +131 -0
  30. data/spec/boltless/extensions/operations_spec.rb +189 -0
  31. data/spec/boltless/extensions/transactions_spec.rb +418 -0
  32. data/spec/boltless/extensions/utilities_spec.rb +546 -0
  33. data/spec/boltless/request_spec.rb +946 -0
  34. data/spec/boltless/result_row_spec.rb +161 -0
  35. data/spec/boltless/result_spec.rb +127 -0
  36. data/spec/boltless/statement_collector_spec.rb +45 -0
  37. data/spec/boltless/transaction_spec.rb +601 -0
  38. data/spec/boltless_spec.rb +11 -0
  39. data/spec/fixtures/files/raw_result.yml +21 -0
  40. data/spec/fixtures/files/raw_result_with_graph_result.yml +48 -0
  41. data/spec/fixtures/files/raw_result_with_meta.yml +11 -0
  42. data/spec/fixtures/files/raw_result_with_stats.yml +26 -0
  43. data/spec/spec_helper.rb +89 -0
  44. metadata +384 -0
@@ -0,0 +1,601 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Boltless::Transaction do
6
+ let(:new_instance) { ->(**args) { described_class.new(connection, **args) } }
7
+ let(:instance) { new_instance.call }
8
+ let(:connection) { Boltless.connection_pool.checkout }
9
+ let(:request) { instance.instance_variable_get(:@request) }
10
+ let(:req_err) { ->(*) { raise Boltless::Errors::RequestError, 'test' } }
11
+ let(:res_err) { ->(*) { raise Boltless::Errors::ResponseError, 'test' } }
12
+ let(:raw_state) { nil }
13
+
14
+ before do
15
+ # Only force the raw state when it is configured
16
+ instance.instance_variable_set(:@raw_state, raw_state) if raw_state
17
+ end
18
+
19
+ describe 'full workflow' do
20
+ let(:opts) { {} }
21
+ let(:failed_action) do
22
+ tx = new_instance[**opts]
23
+ tx.begin
24
+ tx.run('CREATE (n:User { name: $name })', name: 'Klaus')
25
+ tx.run('SOME THING!')
26
+ tx.commit
27
+ end
28
+ let(:check_action) do
29
+ tx = new_instance[**opts]
30
+ tx.begin
31
+ tx.run('MATCH (n:User) RETURN n.name').tap do
32
+ tx.commit
33
+ end
34
+ end
35
+ let(:write_action) do
36
+ tx = new_instance[**opts]
37
+ tx.begin!
38
+ tx.run!('CREATE (n:User { name: $name })', name: 'Klaus')
39
+ tx.commit!
40
+ end
41
+
42
+ it 'rolls back the transaction on errors' do
43
+ failed_action
44
+ expect(check_action.count).to be_eql(0)
45
+ end
46
+
47
+ it 'returns a mapped result' do
48
+ expect(check_action).to be_a(Boltless::Result)
49
+ end
50
+
51
+ describe 'with raw results' do
52
+ let(:opts) { { raw_results: true } }
53
+
54
+ it 'returns an unmapped result' do
55
+ expect(check_action).to be_a(Hash)
56
+ end
57
+ end
58
+
59
+ describe 'with the read access mode' do
60
+ let(:opts) { { access_mode: :read } }
61
+
62
+ it 'does not allow us to perform write operations' do
63
+ expect { write_action }.to \
64
+ raise_error(Boltless::Errors::TransactionRollbackError,
65
+ /Neo.ClientError.Request.Invalid/i)
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'delegations' do
71
+ it 'allows to access the #build_cypher utility' do
72
+ expect(instance.respond_to?(:build_cypher)).to be_eql(true)
73
+ end
74
+ end
75
+
76
+ describe '#initialize' do
77
+ it 'passes down the connection to the request' do
78
+ expect(Boltless::Request).to \
79
+ receive(:new).with(connection, anything)
80
+ described_class.new(connection)
81
+ end
82
+
83
+ it 'passes down the access mode to the request' do
84
+ expect(Boltless::Request).to \
85
+ receive(:new).with(connection, a_hash_including(access_mode: :read))
86
+ described_class.new(connection, access_mode: :read)
87
+ end
88
+
89
+ it 'passes down the database to the request' do
90
+ expect(Boltless::Request).to \
91
+ receive(:new).with(connection, a_hash_including(database: 'test'))
92
+ described_class.new(connection, database: 'test')
93
+ end
94
+
95
+ it 'passes down the raw results flag to the request' do
96
+ expect(Boltless::Request).to \
97
+ receive(:new).with(connection, a_hash_including(raw_results: true))
98
+ described_class.new(connection, raw_results: true)
99
+ end
100
+
101
+ context 'with unknown access mode' do
102
+ it 'raises an ArgumentError' do
103
+ expect { described_class.new(connection, access_mode: :unknown) }.to \
104
+ raise_error(
105
+ ArgumentError,
106
+ /Unknown access mode 'unknown'.*use ':read' or ':write'/i
107
+ )
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#access_mode' do
113
+ let(:action) { instance.access_mode }
114
+
115
+ context 'when not explictly configured' do
116
+ it 'returns write' do
117
+ expect(action).to be_eql(:write)
118
+ end
119
+ end
120
+
121
+ context 'when initialized with read' do
122
+ let(:instance) { new_instance[access_mode: :read] }
123
+
124
+ it 'returns read' do
125
+ expect(action).to be_eql(:read)
126
+ end
127
+ end
128
+
129
+ context 'when initialized with write' do
130
+ let(:instance) { new_instance[access_mode: :write] }
131
+
132
+ it 'returns write' do
133
+ expect(action).to be_eql(:write)
134
+ end
135
+ end
136
+ end
137
+
138
+ describe '#id' do
139
+ let(:action) { instance.id }
140
+
141
+ context 'when not yet started' do
142
+ it 'returns nil' do
143
+ expect(action).to be(nil)
144
+ end
145
+ end
146
+
147
+ context 'when started' do
148
+ before { instance.begin! }
149
+
150
+ it 'returns an Integer' do
151
+ expect(action).to be_a(Integer)
152
+ end
153
+ end
154
+ end
155
+
156
+ describe '#raw_state' do
157
+ let(:action) { instance.raw_state }
158
+
159
+ it 'returns an Symbol' do
160
+ expect(action).to be_a(Symbol)
161
+ end
162
+
163
+ describe 'after initialization' do
164
+ it 'returns not_yet_started' do
165
+ expect(action).to be_eql(:not_yet_started)
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#state' do
171
+ let(:action) { instance.state }
172
+
173
+ it 'returns an ActiveSupport::StringInquirer' do
174
+ expect(action).to be_a(ActiveSupport::StringInquirer)
175
+ end
176
+
177
+ describe 'after initialization' do
178
+ it 'returns not_yet_started' do
179
+ expect(action).to be_eql('not_yet_started')
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#begin!' do
185
+ let(:action) { instance.begin! }
186
+ let(:instance) { new_instance[access_mode: :read] }
187
+
188
+ before { allow(request).to receive(:begin_transaction) }
189
+
190
+ context 'when the transaction is not in a usable state' do
191
+ let(:raw_state) { :closed }
192
+
193
+ it 'raises a Boltless::Errors::TransactionInBadStateError' do
194
+ expect { action }.to \
195
+ raise_error(Boltless::Errors::TransactionInBadStateError,
196
+ /Transaction already closed/i)
197
+ end
198
+ end
199
+
200
+ it 'call the #begin_transaction on the request' do
201
+ expect(request).to receive(:begin_transaction).once
202
+ action
203
+ end
204
+
205
+ it 'returns true' do
206
+ expect(action).to be_eql(true)
207
+ end
208
+
209
+ it 'switches the state to open' do
210
+ expect { action }.to \
211
+ change(instance, :state).from('not_yet_started').to('open')
212
+ end
213
+ end
214
+
215
+ describe '#begin' do
216
+ let(:action) { instance.begin }
217
+
218
+ context 'when the transaction is not in a usable state' do
219
+ let(:raw_state) { :closed }
220
+
221
+ it 'returns false' do
222
+ expect(action).to be_eql(false)
223
+ end
224
+ end
225
+
226
+ it 'wraps the bang-variant in a #handle_errors call' do
227
+ expect(instance).to receive(:handle_errors).with(false)
228
+ action
229
+ end
230
+
231
+ context 'with errors' do
232
+ before { allow(instance).to receive(:begin!, &res_err) }
233
+
234
+ it 'returns false' do
235
+ expect(action).to be_eql(false)
236
+ end
237
+ end
238
+
239
+ context 'without errors' do
240
+ before { allow(request).to receive(:begin_transaction) }
241
+
242
+ it 'returns true' do
243
+ expect(action).to be_eql(true)
244
+ end
245
+
246
+ it 'switches the state to open' do
247
+ expect { action }.to \
248
+ change(instance, :state).from('not_yet_started').to('open')
249
+ end
250
+ end
251
+ end
252
+
253
+ describe '#run!' do
254
+ let(:action) { instance.run!('RETURN 1') }
255
+
256
+ context 'when the transaction is not in a usable state' do
257
+ let(:raw_state) { :closed }
258
+
259
+ it 'raises a Boltless::Errors::TransactionInBadStateError' do
260
+ expect { action }.to \
261
+ raise_error(Boltless::Errors::TransactionInBadStateError,
262
+ /Transaction not open/i)
263
+ end
264
+ end
265
+
266
+ context 'with open state' do
267
+ before { instance.begin! }
268
+
269
+ it 'calls the #run_query on the request' do
270
+ expect(request).to \
271
+ receive(:run_query).with(instance.id, Hash).and_return([])
272
+ action
273
+ end
274
+
275
+ it 'returns the result' do
276
+ res = instance.run!('RETURN date() AS date')
277
+ expect(res.value).to be_eql(Date.today.to_s)
278
+ end
279
+ end
280
+ end
281
+
282
+ describe '#run' do
283
+ let(:action) { instance.run('RETURN date()') }
284
+ let(:raw_state) { :open }
285
+
286
+ context 'when the transaction is not in a usable state' do
287
+ let(:raw_state) { :closed }
288
+
289
+ it 'returns nil' do
290
+ expect(action).to be_eql(nil)
291
+ end
292
+ end
293
+
294
+ it 'wraps the bang-variant in a #handle_errors call' do
295
+ expect(instance).to receive(:handle_errors)
296
+ action
297
+ end
298
+
299
+ context 'with errors' do
300
+ before { allow(instance).to receive(:run!, &res_err) }
301
+
302
+ it 'returns nil' do
303
+ expect(action).to be_eql(nil)
304
+ end
305
+ end
306
+
307
+ context 'without errors' do
308
+ before { allow(request).to receive(:run_query).and_return([123]) }
309
+
310
+ it 'returns an the first result' do
311
+ expect(action).to be_eql(123)
312
+ end
313
+ end
314
+ end
315
+
316
+ describe '#run_in_batch!' do
317
+ let(:action) { instance.run_in_batch!(['RETURN 1'], ['RETURN date()']) }
318
+
319
+ context 'when the transaction is not in a usable state' do
320
+ let(:raw_state) { :closed }
321
+
322
+ it 'raises a Boltless::Errors::TransactionInBadStateError' do
323
+ expect { action }.to \
324
+ raise_error(Boltless::Errors::TransactionInBadStateError,
325
+ /Transaction not open/i)
326
+ end
327
+ end
328
+
329
+ context 'with open state' do
330
+ let(:statements) do
331
+ [
332
+ {
333
+ statement: 'RETURN 1',
334
+ parameters: {}
335
+ },
336
+ {
337
+ statement: 'RETURN date()',
338
+ parameters: {}
339
+ }
340
+ ]
341
+ end
342
+
343
+ before { instance.begin! }
344
+
345
+ it 'calls the #run_query on the request' do
346
+ expect(request).to receive(:run_query).with(instance.id, *statements)
347
+ action
348
+ end
349
+
350
+ it 'returns two results (one for each statement)' do
351
+ expect(action.count).to be_eql(2)
352
+ end
353
+
354
+ it 'returns the correct result (first statement)' do
355
+ expect(action.first.value).to be_eql(1)
356
+ end
357
+
358
+ it 'returns the correct result (second statement)' do
359
+ expect(action.last.value).to be_eql(Date.today.to_s)
360
+ end
361
+ end
362
+ end
363
+
364
+ describe '#run_in_batch' do
365
+ let(:action) { instance.run_in_batch(['RETURN date()'], ['RETURN 1']) }
366
+ let(:raw_state) { :open }
367
+
368
+ context 'when the transaction is not in a usable state' do
369
+ let(:raw_state) { :closed }
370
+
371
+ it 'returns nil' do
372
+ expect(action).to be_eql(nil)
373
+ end
374
+ end
375
+
376
+ it 'wraps the bang-variant in a #handle_errors call' do
377
+ expect(instance).to receive(:handle_errors)
378
+ action
379
+ end
380
+
381
+ context 'with errors' do
382
+ before { allow(instance).to receive(:run_in_batch!, &res_err) }
383
+
384
+ it 'returns nil' do
385
+ expect(action).to be_eql(nil)
386
+ end
387
+ end
388
+
389
+ context 'without errors' do
390
+ before { allow(request).to receive(:run_query).and_return([]) }
391
+
392
+ it 'returns an empty array' do
393
+ expect(action).to match_array([])
394
+ end
395
+ end
396
+ end
397
+
398
+ describe '#commit!' do
399
+ let(:action) { instance.commit! }
400
+ let(:instance) { new_instance[access_mode: :read] }
401
+
402
+ context 'when the transaction is not in a usable state' do
403
+ let(:raw_state) { :closed }
404
+
405
+ it 'raises a Boltless::Errors::TransactionInBadStateError' do
406
+ expect { action }.to \
407
+ raise_error(Boltless::Errors::TransactionInBadStateError,
408
+ /Transaction not open/i)
409
+ end
410
+ end
411
+
412
+ context 'with open state' do
413
+ before { instance.begin! }
414
+
415
+ it 'calls the #commit_transaction on the request' do
416
+ expect(request).to receive(:commit_transaction).with(instance.id)
417
+ action
418
+ end
419
+
420
+ it 'allows to send finalizing statements' do
421
+ res = instance.commit!(['RETURN date() AS date'])
422
+ expect(res.first.value).to be_eql(Date.today.to_s)
423
+ end
424
+
425
+ it 'switches the state to closed' do
426
+ expect { action }.to \
427
+ change(instance, :state).from('open').to('closed')
428
+ end
429
+ end
430
+ end
431
+
432
+ describe '#commit' do
433
+ let(:action) { instance.commit }
434
+ let(:raw_state) { :open }
435
+
436
+ context 'when the transaction is not in a usable state' do
437
+ let(:raw_state) { :closed }
438
+
439
+ it 'returns nil' do
440
+ expect(action).to be_eql(nil)
441
+ end
442
+ end
443
+
444
+ it 'wraps the bang-variant in a #handle_errors call' do
445
+ expect(instance).to receive(:handle_errors)
446
+ action
447
+ end
448
+
449
+ context 'with errors' do
450
+ before { allow(instance).to receive(:commit!, &res_err) }
451
+
452
+ it 'returns nil' do
453
+ expect(action).to be_eql(nil)
454
+ end
455
+ end
456
+
457
+ context 'without errors' do
458
+ before { allow(request).to receive(:commit_transaction).and_return([]) }
459
+
460
+ it 'returns an empty array' do
461
+ expect(action).to match_array([])
462
+ end
463
+
464
+ it 'switches the state to closed' do
465
+ expect { action }.to \
466
+ change(instance, :state).from('open').to('closed')
467
+ end
468
+ end
469
+ end
470
+
471
+ describe '#rollback!' do
472
+ let(:action) { instance.rollback! }
473
+ let(:instance) { new_instance[access_mode: :read] }
474
+
475
+ context 'when the transaction is not in a usable state' do
476
+ let(:raw_state) { :closed }
477
+
478
+ it 'raises a Boltless::Errors::TransactionInBadStateError' do
479
+ expect { action }.to \
480
+ raise_error(Boltless::Errors::TransactionInBadStateError,
481
+ /Transaction not open/i)
482
+ end
483
+ end
484
+
485
+ context 'with open state' do
486
+ before { instance.begin! }
487
+
488
+ it 'calls the #rollback_transaction on the request' do
489
+ expect(request).to receive(:rollback_transaction).with(instance.id)
490
+ action
491
+ end
492
+
493
+ it 'returns true' do
494
+ expect(action).to be_eql(true)
495
+ end
496
+
497
+ it 'switches the state to closed' do
498
+ expect { action }.to \
499
+ change(instance, :state).from('open').to('closed')
500
+ end
501
+ end
502
+ end
503
+
504
+ describe '#rollback' do
505
+ let(:action) { instance.rollback }
506
+ let(:raw_state) { :open }
507
+
508
+ context 'when the transaction is not in a usable state' do
509
+ let(:raw_state) { :closed }
510
+
511
+ it 'returns false' do
512
+ expect(action).to be_eql(false)
513
+ end
514
+ end
515
+
516
+ it 'wraps the bang-variant in a #handle_errors call' do
517
+ expect(instance).to receive(:handle_errors).with(false)
518
+ action
519
+ end
520
+
521
+ context 'with errors' do
522
+ before { allow(instance).to receive(:rollback!, &res_err) }
523
+
524
+ it 'returns false' do
525
+ expect(action).to be_eql(false)
526
+ end
527
+ end
528
+
529
+ context 'without errors' do
530
+ before { allow(request).to receive(:rollback_transaction) }
531
+
532
+ it 'returns true' do
533
+ expect(action).to be_eql(true)
534
+ end
535
+
536
+ it 'switches the state to closed' do
537
+ expect { action }.to \
538
+ change(instance, :state).from('open').to('closed')
539
+ end
540
+ end
541
+ end
542
+
543
+ describe '#handle_errors' do
544
+ let(:action) { ->(*args, &block) { instance.handle_errors(*args, &block) } }
545
+
546
+ it 'yields the user given block' do
547
+ expect { |control| action.call(&control) }.to yield_control
548
+ end
549
+
550
+ it 'rescues Boltless::Errors::RequestError' do
551
+ expect { action.call(&req_err) }.not_to raise_error
552
+ end
553
+
554
+ it 'rescues Boltless::Errors::ResponseError' do
555
+ expect { action.call(&res_err) }.not_to raise_error
556
+ end
557
+
558
+ it 'does not rescue ArgumentError' do
559
+ expect { action.call { raise ArgumentError } }.to \
560
+ raise_error(ArgumentError)
561
+ end
562
+
563
+ it 'returns the given error result in case of errors (non-proc)' do
564
+ expect(action.call(true, &res_err)).to be_eql(true)
565
+ end
566
+
567
+ it 'returns the given error result in case of errors (proc)' do
568
+ err_res = ->(e) { "Err: #{e.message}" }
569
+ expect(action.call(err_res, &res_err)).to be_eql('Err: test')
570
+ end
571
+
572
+ it 'returns the result of the user given block when no errors occur' do
573
+ expect(action.call { 123 }).to be_eql(123)
574
+ end
575
+
576
+ it 'switches the state to closed on errors' do
577
+ expect { action.call(&res_err) }.to \
578
+ change(instance, :state).from('not_yet_started').to('closed')
579
+ end
580
+
581
+ it 'calls #cleanup on errors' do
582
+ expect(instance).to receive(:cleanup).once
583
+ action.call(&res_err)
584
+ end
585
+ end
586
+
587
+ describe '#cleanup' do
588
+ let(:action) { instance.cleanup }
589
+
590
+ it 'clears the request instance variable' do
591
+ expect { action }.to \
592
+ change { instance.instance_variable_get(:@request) }
593
+ .from(Boltless::Request).to(nil)
594
+ end
595
+
596
+ it 'changes the state to cleaned' do
597
+ expect { action }.to \
598
+ change(instance, :state).from('not_yet_started').to('cleaned')
599
+ end
600
+ end
601
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Boltless do
6
+ before { described_class.reset_configuration! }
7
+
8
+ it 'has a version number' do
9
+ expect(Boltless::VERSION).not_to be nil
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ ---
2
+ columns:
3
+ - name
4
+ - birthday
5
+ - written_books
6
+ - active
7
+ data:
8
+ - row:
9
+ - Bernd
10
+ - 1971-07-28
11
+ - 2
12
+ - true
13
+ meta:
14
+ -
15
+ - row:
16
+ - Klaus
17
+ - 1998-01-03
18
+ - 0
19
+ - false
20
+ meta:
21
+ -
@@ -0,0 +1,48 @@
1
+ # CREATE (n:User { name: $name }) RETURN n [result_as_graph: true]
2
+ # MATCH (n:User) RETURN n
3
+ ---
4
+ columns:
5
+ - n
6
+ data:
7
+ - row:
8
+ - name: Kalle
9
+ meta:
10
+ - id: 149
11
+ type: node
12
+ deleted: false
13
+ graph:
14
+ nodes:
15
+ - id: '149'
16
+ labels:
17
+ - User
18
+ properties:
19
+ name: Kalle
20
+ relationships: []
21
+ - row:
22
+ - name: Klaus
23
+ meta:
24
+ - id: 147
25
+ type: node
26
+ deleted: false
27
+ graph:
28
+ nodes:
29
+ - id: '147'
30
+ labels:
31
+ - User
32
+ properties:
33
+ name: Klaus
34
+ relationships: []
35
+ - row:
36
+ - name: Bernd
37
+ meta:
38
+ - id: 148
39
+ type: node
40
+ deleted: false
41
+ graph:
42
+ nodes:
43
+ - id: '148'
44
+ labels:
45
+ - User
46
+ properties:
47
+ name: Bernd
48
+ relationships: []
@@ -0,0 +1,11 @@
1
+ # CREATE (n:User { name: $name }) RETURN n
2
+ ---
3
+ columns:
4
+ - n
5
+ data:
6
+ - row:
7
+ - name: Klaus
8
+ meta:
9
+ - id: 145
10
+ type: node
11
+ deleted: false
@@ -0,0 +1,26 @@
1
+ # CREATE (n:User { name: $name }) RETURN n [with_stats: true]
2
+ ---
3
+ columns:
4
+ - n
5
+ data:
6
+ - row:
7
+ - name: Klaus
8
+ meta:
9
+ - id: 146
10
+ type: node
11
+ deleted: false
12
+ stats:
13
+ contains_updates: true
14
+ nodes_created: 1
15
+ nodes_deleted: 0
16
+ properties_set: 1
17
+ relationships_created: 0
18
+ relationship_deleted: 0
19
+ labels_added: 1
20
+ labels_removed: 0
21
+ indexes_added: 0
22
+ indexes_removed: 0
23
+ constraints_added: 0
24
+ constraints_removed: 0
25
+ contains_system_updates: false
26
+ system_updates: 0