grumlin 0.21.1 → 0.22.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95963424bb38728bb21c2d3746a93be60daf303b2c2897b24d99684fd1fb7bff
4
- data.tar.gz: 809b4b589b2bc5fd8687f0f63a6f73f17aab82060990b3a5942164c2ac92ef83
3
+ metadata.gz: 519d93b97b2cd8f07a1e11e75e45b0b7ca780947e82b3624d5e6c7c6d719ec27
4
+ data.tar.gz: 5eed9eab9e99fe02246995adbc8c01988bd21d150df234946a575d01cda518f2
5
5
  SHA512:
6
- metadata.gz: b095f3958ebf4b70577e607a0b10e27fc073055fe08d6fe55787c71fd104e77cf78cdcd0a54d06e7881c9183cab479ef6e92e6117a1c1b418945d1ebb417655c
7
- data.tar.gz: 0477a8ce8ccfc5ea1cc3b95b7b48501b05387207918478adc4eecb8e286e8495910b6bd718e1d5c00ab63e61e02ca6e89212913ec35d8df49b267cb75281e9eb
6
+ metadata.gz: 76cd4990acd94d119b2a716e25b10d35bdc970e794a77de4d25dcc7e3f67f5129236ff1c037a4f0f0c3208643afca9695b6dc602bb84ae01d61a84c1b89364fe
7
+ data.tar.gz: afda51dc7010f1fc508e96b203727ebee971597b6425aa2ec29932d7957e1b2b12d8959526a5fc10d92e46926e94dab1ca3d62eb583bbb36498f99bbd3e11689
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.21.1)
4
+ grumlin (0.22.0)
5
5
  async-pool (~> 0.3)
6
6
  async-websocket (~> 0.19)
7
7
  oj (~> 3.13)
data/README.md CHANGED
@@ -64,8 +64,7 @@ explicitly specify the provider you use.
64
64
  #### Provider features
65
65
 
66
66
  Every provider is described by a set of features. In the future `Grumlin` may decide to disable or enable
67
- some parts of it's functionality to comply provider's supported features. Currently there is no difference
68
- in behaviour when working with different providers.
67
+ some parts of it's functionality to comply provider's supported features.
69
68
 
70
69
  To check current providers supported features use
71
70
 
@@ -73,6 +72,12 @@ To check current providers supported features use
73
72
  Grumlin.features
74
73
  ```
75
74
 
75
+ Current differences between providers:
76
+
77
+ | Feature | TinkerGraph |AWS Neptune|
78
+ |--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|
79
+ | Transactions | Transaction semantic is ignoroed, data is always writen, `tx.rollback` does nothing, an info is printed every time transactions are used with TinkerGraph |Full support
80
+
76
81
  ### Traversing graphs
77
82
 
78
83
  **Warning**: Not all steps and expressions defined in the reference documentation are supported.
@@ -240,21 +245,28 @@ Each `return_mode` is mapped to a particular termination step:
240
245
  - `:traversal` - do not execute the query and return the traversal as is
241
246
 
242
247
  `Grumlin::Repository` also provides a set of generic CRUD operations:
243
- - `add_vertex(label, id = nil, **properties)`
244
- - `add_edge(label, id = nil, from:, to:, **properties)`
245
- - `drop_vertex(id)`
246
- - `drop_edge(id = nil, from: nil, to: nil, label: nil)`
248
+ - `add_vertex(label, id = nil, start: g, **properties)`
249
+ - `add_edge(label, id = nil, from:, to:, start: g, **properties)`
250
+ - `drop_vertex(id, start: g)`
251
+ - `drop_edge(id = nil, from: nil, to: nil, label: nil, start: g)`
247
252
 
248
253
  and a few methods that emulate upserts:
249
- - `upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, **params)`
250
- - `upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, **params)`
251
- - `upsert_edges(edges, batch_size: 100, on_failure: :retry, **params)`
252
- - `upsert_vertices(edges, batch_size: 100, on_failure: :retry, **params)`
254
+ - `upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`
255
+ - `upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`
256
+ - `upsert_edges(edges, batch_size: 100, on_failure: :retry, start: g, **params)`
257
+ - `upsert_vertices(edges, batch_size: 100, on_failure: :retry, start: g, **params)`
253
258
 
254
259
  All of them support 3 different modes for error handling: `:retry`, `:ignore` and `:raise`. Retry mode is implemented
255
260
  with [retryable](https://github.com/nfedyashev/retryable). **params will be merged to the default config for upserts
256
261
  and passed to `Retryable.retryable`. In case if you want to modify retryable behaviour you are to do so.
257
262
 
263
+ If you want to use these methods inside a transaction simply pass your `gtx` as `start` parameter:
264
+ ```ruby
265
+ g.tx do |gtx|
266
+ add_vertex(:vertex, start: gtx)
267
+ end
268
+ ```
269
+
258
270
  If you don't want to define you own repository, simply use
259
271
 
260
272
  `Grumlin::Repository.new` returns an instance of an anonymous class extending `Grumlin::Repository`.
@@ -282,6 +294,24 @@ it may be useful for debugging. Note that one needs to call a termination step m
282
294
 
283
295
  method will return profiling data of the results.
284
296
 
297
+ #### Transactions
298
+
299
+ Since 0.22.0 `Grumlin` supports transactions when working with providers that supports them:
300
+ ```ruby
301
+ # Using Transaction directly
302
+ tx = g.tx
303
+ gtx = tx.begin
304
+ gtx.addV(:vertex).iterate
305
+ tx.commit # or tx.rollback
306
+
307
+ # Using with a block
308
+ g.tx do |gtx|
309
+ gtx.addV(:vertex).iterate
310
+ # raise Grumlin::Rollback to manually rollback
311
+ # any other exception will also rollback the transaction and will be reraised
312
+ end # commits automatically
313
+ ```
314
+
285
315
  #### IRB
286
316
 
287
317
  An example of how to start an IRB session with support for executing gremlin queries:
@@ -16,7 +16,7 @@ module Grumlin
16
16
  end
17
17
 
18
18
  def configuration_step?
19
- CONFIGURATION_STEPS.include?(@name)
19
+ CONFIGURATION_STEPS.include?(@name) || name.to_sym == :tx
20
20
  end
21
21
 
22
22
  def start_step?
@@ -30,12 +30,6 @@ module Grumlin
30
30
  @count += 1
31
31
  end
32
32
 
33
- def finalize_tx(action, session_id)
34
- @client.finalize_tx(action, session_id)
35
- ensure
36
- @count += 1
37
- end
38
-
39
33
  def viable?
40
34
  !closed?
41
35
  end
@@ -104,15 +98,15 @@ module Grumlin
104
98
  raise NotConnectedError unless connected?
105
99
 
106
100
  request = to_query(bytecode, session_id: session_id)
107
- submit_request(request)
108
- end
109
101
 
110
- def finalize_tx(action, session_id)
111
- raise NotConnectedError unless connected?
112
- raise ArgumentError, "session_id cannot be nil" if session_id.nil?
113
-
114
- request = finalize_tx_query(action, session_id)
115
- submit_request(request)
102
+ channel = @request_dispatcher.add_request(request)
103
+ begin
104
+ @transport.write(request)
105
+ channel.dequeue.flat_map { |item| Typing.cast(item) }
106
+ rescue Async::Stop, Async::TimeoutError
107
+ close(check_requests: false)
108
+ raise
109
+ end
116
110
  end
117
111
 
118
112
  def inspect
@@ -125,18 +119,6 @@ module Grumlin
125
119
 
126
120
  private
127
121
 
128
- def submit_request(request)
129
- channel = @request_dispatcher.add_request(request)
130
- @transport.write(request)
131
-
132
- begin
133
- channel.dequeue.flat_map { |item| Typing.cast(item) }
134
- rescue Async::Stop, Async::TimeoutError
135
- close(check_requests: false)
136
- raise
137
- end
138
- end
139
-
140
122
  # This might be overridden in successors
141
123
  def build_transport
142
124
  Transport.new(@url, parent: @parent, **@client_options)
@@ -157,21 +139,5 @@ module Grumlin
157
139
  }.compact
158
140
  }
159
141
  end
160
-
161
- def finalize_tx_query(action, session_id)
162
- {
163
- requestId: SecureRandom.uuid,
164
- op: :bytecode,
165
- processor: session_id ? :session : :traversal,
166
- args: {
167
- gremlin: {
168
- :@type => "g:Bytecode",
169
- :@value => { source: [[:tx, action]] }
170
- },
171
- aliases: { g: :g },
172
- session: session_id
173
- }.compact
174
- }
175
- end
176
142
  end
177
143
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class DummyTransaction < Transaction
5
+ attr_reader :uuid
6
+
7
+ include Console
8
+
9
+ def initialize(traversal_start_class, pool: Grumlin.default_pool) # rubocop:disable Lint/MissingSuper, Lint/UnusedMethodArgument
10
+ @traversal_start_class = traversal_start_class
11
+
12
+ logger.info(self) do
13
+ "#{Grumlin.config.provider} does not support transactions. commit and rollback are ignored, data will be saved"
14
+ end
15
+ end
16
+
17
+ def commit
18
+ nil
19
+ end
20
+
21
+ def rollback
22
+ nil
23
+ end
24
+ end
25
+ end
@@ -22,51 +22,51 @@ module Grumlin
22
22
  self.class.shortcuts
23
23
  end
24
24
 
25
- def drop_vertex(id)
26
- g.V(id).drop.iterate
25
+ def drop_vertex(id, start: g)
26
+ start.V(id).drop.iterate
27
27
  end
28
28
 
29
- def drop_edge(id = nil, from: nil, to: nil, label: nil) # rubocop:disable Metrics/AbcSize
29
+ def drop_edge(id = nil, from: nil, to: nil, label: nil, start: g) # rubocop:disable Metrics/AbcSize
30
30
  raise ArgumentError, "either id or from:, to: and label: must be passed" if [id, from, to, label].all?(&:nil?)
31
- return g.E(id).drop.iterate unless id.nil?
31
+ return start.E(id).drop.iterate unless id.nil?
32
32
 
33
33
  raise ArgumentError, "from:, to: and label: must be passed" if [from, to, label].any?(&:nil?)
34
34
 
35
- g.V(from).outE(label).where(__.inV.hasId(to)).limit(1).drop.iterate
35
+ start.V(from).outE(label).where(__.inV.hasId(to)).limit(1).drop.iterate
36
36
  end
37
37
 
38
- def add_vertex(label, id = nil, **properties)
38
+ def add_vertex(label, id = nil, start: g, **properties)
39
39
  id ||= properties[T.id]
40
40
  properties = except(properties, T.id)
41
41
 
42
- t = g.addV(label)
42
+ t = start.addV(label)
43
43
  t = t.props(T.id => id) unless id.nil?
44
44
  t.props(**properties).next
45
45
  end
46
46
 
47
- def add_edge(label, id = nil, from:, to:, **properties)
47
+ def add_edge(label, id = nil, from:, to:, start: g, **properties)
48
48
  id ||= properties[T.id]
49
49
  properties = except(properties, T.label)
50
50
  properties[T.id] = id
51
51
 
52
- g.addE(label).from(__.V(from)).to(__.V(to)).props(**properties).next
52
+ start.addE(label).from(__.V(from)).to(__.V(to)).props(**properties).next
53
53
  end
54
54
 
55
- def upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, **params)
55
+ def upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params) # rubocop:disable Metrics/ParameterLists
56
56
  with_upsert_error_handling(on_failure, params) do
57
57
  create_properties, update_properties = cleanup_properties(create_properties, update_properties)
58
58
 
59
- g.upsertV(label, id, create_properties, update_properties).id.next
59
+ start.upsertV(label, id, create_properties, update_properties).id.next
60
60
  end
61
61
  end
62
62
 
63
63
  # vertices:
64
64
  # [["label", "id", {create: :properties}, {update: properties}]]
65
65
  # params can override Retryable config from UPSERT_RETRY_PARAMS
66
- def upsert_vertices(vertices, batch_size: 100, on_failure: :retry, **params)
66
+ def upsert_vertices(vertices, batch_size: 100, on_failure: :retry, start: g, **params)
67
67
  vertices.each_slice(batch_size) do |slice|
68
68
  with_upsert_error_handling(on_failure, params) do
69
- slice.reduce(g) do |t, (label, id, create_properties, update_properties)|
69
+ slice.reduce(start) do |t, (label, id, create_properties, update_properties)|
70
70
  create_properties, update_properties = cleanup_properties(create_properties, update_properties)
71
71
 
72
72
  t.upsertV(label, id, create_properties, update_properties)
@@ -77,20 +77,21 @@ module Grumlin
77
77
 
78
78
  # Only from and to are used to find the existing edge, if one wants to assign an id to a created edge,
79
79
  # it must be passed as T.id in create_properties.
80
- def upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, **params) # rubocop:disable Metrics/ParameterLists
80
+ def upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, # rubocop:disable Metrics/ParameterLists
81
+ on_failure: :retry, start: g, **params)
81
82
  with_upsert_error_handling(on_failure, params) do
82
83
  create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
83
- g.upsertE(label, from, to, create_properties, update_properties).id.next
84
+ start.upsertE(label, from, to, create_properties, update_properties).id.next
84
85
  end
85
86
  end
86
87
 
87
88
  # edges:
88
89
  # [["label", "from", "to", {create: :properties}, {update: properties}]]
89
90
  # params can override Retryable config from UPSERT_RETRY_PARAMS
90
- def upsert_edges(edges, batch_size: 100, on_failure: :retry, **params)
91
+ def upsert_edges(edges, batch_size: 100, on_failure: :retry, start: g, **params)
91
92
  edges.each_slice(batch_size) do |slice|
92
93
  with_upsert_error_handling(on_failure, params) do
93
- slice.reduce(g) do |t, (label, from, to, create_properties, update_properties)|
94
+ slice.reduce(start) do |t, (label, from, to, create_properties, update_properties)|
94
95
  create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
95
96
 
96
97
  t.upsertE(label, from, to, create_properties, update_properties)
data/lib/grumlin/steps.rb CHANGED
@@ -32,7 +32,9 @@ module Grumlin
32
32
  end
33
33
 
34
34
  def add(name, args: [], params: {})
35
- return add_configuration_step(name, args: args, params: params) if CONFIGURATION_STEPS.include?(name)
35
+ if CONFIGURATION_STEPS.include?(name) || name.to_sym == :tx
36
+ return add_configuration_step(name, args: args, params: params)
37
+ end
36
38
 
37
39
  StepData.new(name, args: cast_arguments(args), params: params).tap do |step|
38
40
  @steps << step
@@ -10,17 +10,19 @@ module Grumlin
10
10
 
11
11
  def serialize
12
12
  steps = ShortcutsApplyer.call(@steps)
13
- no_return = @params[:no_return] || false
14
-
15
- {
16
- step: (steps.steps + (no_return ? [NONE_STEP] : [])).map { |s| serialize_step(s) }
17
- }.tap do |v|
18
- v.merge!(source: steps.configuration_steps.map { |s| serialize_step(s) }) if steps.configuration_steps.any?
13
+ no_return = @params.fetch(:no_return, false)
14
+ {}.tap do |result|
15
+ result[:step] = serialize_steps(steps.steps + (no_return ? [NONE_STEP] : [])) if steps.steps.any?
16
+ result[:source] = serialize_steps(steps.configuration_steps) if steps.configuration_steps.any?
19
17
  end
20
18
  end
21
19
 
22
20
  private
23
21
 
22
+ def serialize_steps(steps)
23
+ steps.map { |s| serialize_step(s) }
24
+ end
25
+
24
26
  def serialize_step(step)
25
27
  [step.name].tap do |result|
26
28
  step.args.each do |arg|
@@ -2,49 +2,37 @@
2
2
 
3
3
  module Grumlin
4
4
  class Transaction
5
- attr_reader :uuid
5
+ attr_reader :session_id
6
6
 
7
7
  include Console
8
8
 
9
+ COMMIT = Grumlin::Repository.new.g.step(:tx, :commit).bytecode
10
+ ROLLBACK = Grumlin::Repository.new.g.step(:tx, :rollback).bytecode
11
+
9
12
  def initialize(traversal_start_class, pool: Grumlin.default_pool)
10
13
  @traversal_start_class = traversal_start_class
11
14
  @pool = pool
12
15
 
13
- if supported?
14
- @uuid = SecureRandom.uuid
15
- return
16
- end
17
-
18
- logger.info(self) do
19
- "#{Grumlin.config.provider} does not support transactions. commit and rollback are ignored, data will be saved"
20
- end
21
- end
22
-
23
- def supported?
24
- Grumlin.features.supports_transactions?
16
+ @session_id = SecureRandom.uuid
25
17
  end
26
18
 
27
19
  def begin
28
- @traversal_start_class.new(session_id: @uuid)
20
+ @traversal_start_class.new(session_id: @session_id)
29
21
  end
30
22
 
31
23
  def commit
32
- return unless supported?
33
-
34
- finalize(:commit)
24
+ finalize(COMMIT)
35
25
  end
36
26
 
37
27
  def rollback
38
- return unless supported?
39
-
40
- finalize(:rollback)
28
+ finalize(ROLLBACK)
41
29
  end
42
30
 
43
31
  private
44
32
 
45
33
  def finalize(action)
46
34
  @pool.acquire do |client|
47
- client.finalize_tx(action, @uuid)
35
+ client.write(action, session_id: @session_id)
48
36
  end
49
37
  end
50
38
  end
@@ -5,12 +5,24 @@ module Grumlin
5
5
  include WithExtension
6
6
 
7
7
  class TraversalError < Grumlin::Error; end
8
- class AlreadyBoundToTransationError < TraversalError; end
8
+ class AlreadyBoundToTransactionError < TraversalError; end
9
9
 
10
10
  def tx
11
- raise AlreadyBoundToTransationError if @session_id
11
+ raise AlreadyBoundToTransactionError if @session_id
12
12
 
13
- Transaction.new(self.class, pool: @pool)
13
+ transaction = tx_class.new(self.class, pool: @pool)
14
+ return transaction unless block_given?
15
+
16
+ begin
17
+ yield transaction.begin
18
+ rescue Grumlin::Rollback
19
+ transaction.rollback
20
+ rescue StandardError
21
+ transaction.rollback
22
+ raise
23
+ else
24
+ transaction.commit
25
+ end
14
26
  end
15
27
 
16
28
  def to_s(*)
@@ -20,5 +32,11 @@ module Grumlin
20
32
  def inspect
21
33
  self.class.inspect
22
34
  end
35
+
36
+ private
37
+
38
+ def tx_class
39
+ Grumlin.features.supports_transactions? ? Transaction : DummyTransaction
40
+ end
23
41
  end
24
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.21.1"
4
+ VERSION = "0.22.0"
5
5
  end
data/lib/grumlin.rb CHANGED
@@ -33,6 +33,9 @@ loader.do_not_eager_load(test_helpers)
33
33
  module Grumlin
34
34
  class Error < StandardError; end
35
35
 
36
+ class TransactionError < Error; end
37
+ class Rollback < TransactionError; end
38
+
36
39
  class UnknownError < Error; end
37
40
 
38
41
  class ConnectionError < Error; end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grumlin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.1
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-11 00:00:00.000000000 Z
11
+ date: 2022-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool
@@ -117,6 +117,7 @@ files:
117
117
  - lib/grumlin/benchmark/repository.rb
118
118
  - lib/grumlin/client.rb
119
119
  - lib/grumlin/config.rb
120
+ - lib/grumlin/dummy_transaction.rb
120
121
  - lib/grumlin/edge.rb
121
122
  - lib/grumlin/expressions/cardinality.rb
122
123
  - lib/grumlin/expressions/column.rb