grumlin 0.20.2 → 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 +4 -4
- data/Gemfile.lock +6 -6
- data/README.md +66 -8
- data/gremlin_server/Dockerfile +1 -1
- data/grumlin.gemspec +1 -1
- data/lib/grumlin/action.rb +13 -8
- data/lib/grumlin/client.rb +16 -12
- data/lib/grumlin/config.rb +26 -0
- data/lib/grumlin/dummy_transaction.rb +25 -0
- data/lib/grumlin/features/feature_list.rb +19 -0
- data/lib/grumlin/features/neptune_features.rb +13 -0
- data/lib/grumlin/features/tinkergraph_features.rb +13 -0
- data/lib/grumlin/features.rb +16 -0
- data/lib/grumlin/repository/instance_methods.rb +19 -18
- data/lib/grumlin/repository.rb +7 -0
- data/lib/grumlin/request_dispatcher.rb +4 -43
- data/lib/grumlin/request_error_factory.rb +59 -0
- data/lib/grumlin/shortcuts/properties.rb +5 -4
- data/lib/grumlin/shortcuts/upserts.rb +2 -2
- data/lib/grumlin/steppable.rb +8 -3
- data/lib/grumlin/steps.rb +3 -1
- data/lib/grumlin/steps_serializers/bytecode.rb +8 -6
- data/lib/grumlin/transaction.rb +39 -0
- data/lib/grumlin/traversal_start.rb +27 -0
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin.rb +14 -11
- metadata +11 -4
- data/CHANGELOG.md +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 519d93b97b2cd8f07a1e11e75e45b0b7ca780947e82b3624d5e6c7c6d719ec27
|
4
|
+
data.tar.gz: 5eed9eab9e99fe02246995adbc8c01988bd21d150df234946a575d01cda518f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
4
|
+
grumlin (0.22.0)
|
5
5
|
async-pool (~> 0.3)
|
6
6
|
async-websocket (~> 0.19)
|
7
7
|
oj (~> 3.13)
|
@@ -21,11 +21,11 @@ GEM
|
|
21
21
|
console (~> 1.10)
|
22
22
|
nio4r (~> 2.3)
|
23
23
|
timers (~> 4.1)
|
24
|
-
async-http (0.
|
24
|
+
async-http (0.57.0)
|
25
25
|
async (>= 1.25)
|
26
26
|
async-io (>= 1.28)
|
27
27
|
async-pool (>= 0.2)
|
28
|
-
protocol-http (~> 0.
|
28
|
+
protocol-http (~> 0.23.1)
|
29
29
|
protocol-http1 (~> 0.14.0)
|
30
30
|
protocol-http2 (~> 0.14.0)
|
31
31
|
traces (~> 0.4.0)
|
@@ -37,7 +37,7 @@ GEM
|
|
37
37
|
rspec (~> 3.0)
|
38
38
|
rspec-files (~> 1.0)
|
39
39
|
rspec-memory (~> 1.0)
|
40
|
-
async-websocket (0.19.
|
40
|
+
async-websocket (0.19.2)
|
41
41
|
async-http (~> 0.54)
|
42
42
|
async-io (~> 1.23)
|
43
43
|
protocol-websocket (~> 0.7.0)
|
@@ -72,7 +72,7 @@ GEM
|
|
72
72
|
racc (~> 1.4)
|
73
73
|
nokogiri (1.13.6-x86_64-linux)
|
74
74
|
racc (~> 1.4)
|
75
|
-
oj (3.13.
|
75
|
+
oj (3.13.20)
|
76
76
|
overcommit (0.59.1)
|
77
77
|
childprocess (>= 0.6.3, < 5)
|
78
78
|
iniparse (~> 1.4)
|
@@ -81,7 +81,7 @@ GEM
|
|
81
81
|
parser (3.1.2.0)
|
82
82
|
ast (~> 2.4.1)
|
83
83
|
protocol-hpack (1.4.2)
|
84
|
-
protocol-http (0.
|
84
|
+
protocol-http (0.23.1)
|
85
85
|
protocol-http1 (0.14.4)
|
86
86
|
protocol-http (~> 0.22)
|
87
87
|
protocol-http2 (0.14.2)
|
data/README.md
CHANGED
@@ -46,9 +46,38 @@ Or install it yourself as:
|
|
46
46
|
```ruby
|
47
47
|
Grumlin.configure do |config|
|
48
48
|
config.url = "ws://localhost:8182/gremlin"
|
49
|
+
|
50
|
+
# make sure you select right provider for better compatibility
|
51
|
+
config.provider = :tinkergraph
|
49
52
|
end
|
50
53
|
```
|
51
54
|
|
55
|
+
#### Providers
|
56
|
+
|
57
|
+
Currently `Grumlin` supports 2 providers:
|
58
|
+
- tinkergraph (default)
|
59
|
+
- neptune
|
60
|
+
|
61
|
+
As different providers may have or may have not support for specific features it's recommended to
|
62
|
+
explicitly specify the provider you use.
|
63
|
+
|
64
|
+
#### Provider features
|
65
|
+
|
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.
|
68
|
+
|
69
|
+
To check current providers supported features use
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
Grumlin.features
|
73
|
+
```
|
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
|
+
|
52
81
|
### Traversing graphs
|
53
82
|
|
54
83
|
**Warning**: Not all steps and expressions defined in the reference documentation are supported.
|
@@ -216,21 +245,32 @@ Each `return_mode` is mapped to a particular termination step:
|
|
216
245
|
- `:traversal` - do not execute the query and return the traversal as is
|
217
246
|
|
218
247
|
`Grumlin::Repository` also provides a set of generic CRUD operations:
|
219
|
-
- `add_vertex(label, id = nil, **properties)`
|
220
|
-
- `add_edge(label, id = nil, from:, to:, **properties)`
|
221
|
-
- `drop_vertex(id)`
|
222
|
-
- `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)`
|
223
252
|
|
224
253
|
and a few methods that emulate upserts:
|
225
|
-
- `upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, **params)`
|
226
|
-
- `upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, **params)`
|
227
|
-
- `upsert_edges(edges, batch_size: 100, on_failure: :retry, **params)`
|
228
|
-
- `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)`
|
229
258
|
|
230
259
|
All of them support 3 different modes for error handling: `:retry`, `:ignore` and `:raise`. Retry mode is implemented
|
231
260
|
with [retryable](https://github.com/nfedyashev/retryable). **params will be merged to the default config for upserts
|
232
261
|
and passed to `Retryable.retryable`. In case if you want to modify retryable behaviour you are to do so.
|
233
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
|
+
|
270
|
+
If you don't want to define you own repository, simply use
|
271
|
+
|
272
|
+
`Grumlin::Repository.new` returns an instance of an anonymous class extending `Grumlin::Repository`.
|
273
|
+
|
234
274
|
**Usage**
|
235
275
|
|
236
276
|
To execute the query defined in a query block one simply needs to call a method with the same name:
|
@@ -254,6 +294,24 @@ it may be useful for debugging. Note that one needs to call a termination step m
|
|
254
294
|
|
255
295
|
method will return profiling data of the results.
|
256
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
|
+
|
257
315
|
#### IRB
|
258
316
|
|
259
317
|
An example of how to start an IRB session with support for executing gremlin queries:
|
data/gremlin_server/Dockerfile
CHANGED
data/grumlin.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.metadata["homepage_uri"] = spec.homepage
|
22
22
|
spec.metadata["source_code_uri"] = "https://github.com/zhulik/grumlin"
|
23
|
-
spec.metadata["changelog_uri"] = "https://github.com/
|
23
|
+
spec.metadata["changelog_uri"] = "https://github.com/babbel/grumlin/releases"
|
24
24
|
spec.metadata["rubygems_mfa_required"] = "true"
|
25
25
|
|
26
26
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
data/lib/grumlin/action.rb
CHANGED
@@ -4,18 +4,19 @@ module Grumlin
|
|
4
4
|
class Action < Steppable
|
5
5
|
attr_reader :name, :args, :params, :next_step, :configuration_steps, :previous_step, :shortcut
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
# client is only used when a traversal is a part of transaction
|
8
|
+
def initialize(name, args: [], params: {}, previous_step: nil, pool: Grumlin.default_pool, session_id: nil)
|
9
|
+
super(pool: pool, session_id: session_id)
|
10
|
+
|
9
11
|
@name = name.to_sym
|
10
12
|
@args = args # TODO: add recursive validation: only json types or Action
|
11
13
|
@params = params # TODO: add recursive validation: only json types
|
12
14
|
@previous_step = previous_step
|
13
15
|
@shortcut = shortcuts[@name]
|
14
|
-
@pool = pool || Grumlin.default_pool
|
15
16
|
end
|
16
17
|
|
17
18
|
def configuration_step?
|
18
|
-
CONFIGURATION_STEPS.include?(@name)
|
19
|
+
CONFIGURATION_STEPS.include?(@name) || name.to_sym == :tx
|
19
20
|
end
|
20
21
|
|
21
22
|
def start_step?
|
@@ -73,14 +74,18 @@ module Grumlin
|
|
73
74
|
end
|
74
75
|
|
75
76
|
def toList
|
76
|
-
|
77
|
-
client.write(bytecode)
|
78
|
-
end
|
77
|
+
client_write(bytecode)
|
79
78
|
end
|
80
79
|
|
81
80
|
def iterate
|
81
|
+
client_write(bytecode(no_return: true))
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def client_write(payload)
|
82
87
|
@pool.acquire do |client|
|
83
|
-
client.write(
|
88
|
+
client.write(payload, session_id: @session_id)
|
84
89
|
end
|
85
90
|
end
|
86
91
|
end
|
data/lib/grumlin/client.rb
CHANGED
@@ -24,8 +24,8 @@ module Grumlin
|
|
24
24
|
@client.close
|
25
25
|
end
|
26
26
|
|
27
|
-
def write(bytecode)
|
28
|
-
@client.write(bytecode)
|
27
|
+
def write(bytecode, session_id: nil)
|
28
|
+
@client.write(bytecode, session_id: session_id)
|
29
29
|
ensure
|
30
30
|
@count += 1
|
31
31
|
end
|
@@ -94,14 +94,14 @@ module Grumlin
|
|
94
94
|
end
|
95
95
|
|
96
96
|
# TODO: support yielding
|
97
|
-
def write(bytecode)
|
97
|
+
def write(bytecode, session_id: nil)
|
98
98
|
raise NotConnectedError unless connected?
|
99
99
|
|
100
|
-
request = to_query(bytecode)
|
101
|
-
channel = @request_dispatcher.add_request(request)
|
102
|
-
@transport.write(request)
|
100
|
+
request = to_query(bytecode, session_id: session_id)
|
103
101
|
|
102
|
+
channel = @request_dispatcher.add_request(request)
|
104
103
|
begin
|
104
|
+
@transport.write(request)
|
105
105
|
channel.dequeue.flat_map { |item| Typing.cast(item) }
|
106
106
|
rescue Async::Stop, Async::TimeoutError
|
107
107
|
close(check_requests: false)
|
@@ -124,15 +124,19 @@ module Grumlin
|
|
124
124
|
Transport.new(@url, parent: @parent, **@client_options)
|
125
125
|
end
|
126
126
|
|
127
|
-
def to_query(bytecode)
|
127
|
+
def to_query(bytecode, session_id:)
|
128
128
|
{
|
129
129
|
requestId: SecureRandom.uuid,
|
130
|
-
op:
|
131
|
-
processor:
|
130
|
+
op: :bytecode,
|
131
|
+
processor: session_id ? :session : :traversal,
|
132
132
|
args: {
|
133
|
-
gremlin: {
|
134
|
-
|
135
|
-
|
133
|
+
gremlin: {
|
134
|
+
:@type => "g:Bytecode",
|
135
|
+
:@value => bytecode.serialize
|
136
|
+
},
|
137
|
+
aliases: { g: :g },
|
138
|
+
session: session_id
|
139
|
+
}.compact
|
136
140
|
}
|
137
141
|
end
|
138
142
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
class Config
|
5
|
+
attr_accessor :url, :pool_size, :client_concurrency, :client_factory, :provider
|
6
|
+
|
7
|
+
SUPPORTED_PROVIDERS = %i[neptune tinkergraph].freeze
|
8
|
+
|
9
|
+
class ConfigurationError < Grumlin::Error; end
|
10
|
+
|
11
|
+
class UnknownProviderError < ConfigurationError; end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@pool_size = 10
|
15
|
+
@client_concurrency = 5
|
16
|
+
@provider = :tinkergraph
|
17
|
+
@client_factory = ->(url, parent) { Grumlin::Client.new(url, parent: parent) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate!
|
21
|
+
return if SUPPORTED_PROVIDERS.include?(provider.to_sym)
|
22
|
+
|
23
|
+
raise UnknownProviderError, "provider '#{provider}' is unknown. Supported providers: #{SUPPORTED_PROVIDERS}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
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
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Features
|
5
|
+
class FeatureList
|
6
|
+
def user_supplied_ids?
|
7
|
+
raise(NotImplementedError) if @user_supplied_ids.nil?
|
8
|
+
|
9
|
+
@user_supplied_ids
|
10
|
+
end
|
11
|
+
|
12
|
+
def supports_transactions?
|
13
|
+
raise(NotImplementedError) if @supports_transactions.nil?
|
14
|
+
|
15
|
+
@supports_transactions
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Features
|
5
|
+
class << self
|
6
|
+
FEATURES = {
|
7
|
+
neptune: NeptuneFeatures.new,
|
8
|
+
tinkergraph: TinkergraphFeatures.new
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
def for(provider)
|
12
|
+
FEATURES[provider]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -8,7 +8,7 @@ module Grumlin
|
|
8
8
|
extend Forwardable
|
9
9
|
|
10
10
|
UPSERT_RETRY_PARAMS = {
|
11
|
-
on: [Grumlin::AlreadyExistsError, Grumlin::
|
11
|
+
on: [Grumlin::AlreadyExistsError, Grumlin::ConcurrentModificationError],
|
12
12
|
sleep_method: ->(n) { Async::Task.current.sleep(n) },
|
13
13
|
tries: 3,
|
14
14
|
sleep: ->(n) { (n**2) + 1 + rand }
|
@@ -22,51 +22,51 @@ module Grumlin
|
|
22
22
|
self.class.shortcuts
|
23
23
|
end
|
24
24
|
|
25
|
-
def drop_vertex(id)
|
26
|
-
|
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
|
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
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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(
|
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: {},
|
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
|
-
|
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(
|
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/repository.rb
CHANGED
@@ -10,6 +10,7 @@ module Grumlin
|
|
10
10
|
}.freeze
|
11
11
|
|
12
12
|
def self.extended(base)
|
13
|
+
super
|
13
14
|
base.extend(Grumlin::Shortcuts)
|
14
15
|
base.include(Repository::InstanceMethods)
|
15
16
|
|
@@ -17,6 +18,12 @@ module Grumlin
|
|
17
18
|
base.shortcuts_from(Grumlin::Shortcuts::Upserts)
|
18
19
|
end
|
19
20
|
|
21
|
+
def self.new
|
22
|
+
@repository ||= Class.new do # rubocop:disable Naming/MemoizedInstanceVariableName
|
23
|
+
extend Grumlin::Repository
|
24
|
+
end.new
|
25
|
+
end
|
26
|
+
|
20
27
|
def query(name, return_mode: :list, postprocess_with: nil, &query_block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
21
28
|
return_mode = validate_return_mode!(return_mode)
|
22
29
|
postprocess_with = validate_postprocess_with!(postprocess_with)
|
@@ -10,23 +10,6 @@ module Grumlin
|
|
10
10
|
206 => :partial_content
|
11
11
|
}.freeze
|
12
12
|
|
13
|
-
ERRORS = {
|
14
|
-
499 => InvalidRequestArgumentsError,
|
15
|
-
500 => ServerError,
|
16
|
-
597 => ScriptEvaluationError,
|
17
|
-
599 => ServerSerializationError,
|
18
|
-
598 => ServerTimeoutError,
|
19
|
-
|
20
|
-
401 => ClientSideError,
|
21
|
-
407 => ClientSideError,
|
22
|
-
498 => ClientSideError
|
23
|
-
}.freeze
|
24
|
-
|
25
|
-
VERTEX_ALREADY_EXISTS = "Vertex with id already exists:"
|
26
|
-
EDGE_ALREADY_EXISTS = "Edge with id already exists:"
|
27
|
-
CONCURRENT_VERTEX_INSERT_FAILED = "Failed to complete Insert operation for a Vertex due to conflicting concurrent"
|
28
|
-
CONCURRENT_EDGE_INSERT_FAILED = "Failed to complete Insert operation for an Edge due to conflicting concurrent"
|
29
|
-
|
30
13
|
class DispatcherError < Grumlin::Error; end
|
31
14
|
|
32
15
|
class RequestAlreadyAddedError < DispatcherError; end
|
@@ -47,14 +30,16 @@ module Grumlin
|
|
47
30
|
|
48
31
|
# builds a response object, when it's ready sends it to the client via a channel
|
49
32
|
# TODO: sometimes response does not include requestID, no idea how to handle it so far.
|
50
|
-
def add_response(response) # rubocop:disable Metrics/AbcSize
|
33
|
+
def add_response(response) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
51
34
|
request_id = response[:requestId]
|
52
35
|
raise UnknownRequestError unless ongoing_request?(request_id)
|
53
36
|
|
54
37
|
begin
|
55
38
|
request = @requests[request_id]
|
56
39
|
|
57
|
-
|
40
|
+
RequestErrorFactory.build(request, response).tap do |err|
|
41
|
+
raise err unless err.nil?
|
42
|
+
end
|
58
43
|
|
59
44
|
case SUCCESS[response.dig(:status, :code)]
|
60
45
|
when :success
|
@@ -91,29 +76,5 @@ module Grumlin
|
|
91
76
|
request = @requests.delete(request_id)
|
92
77
|
request[:channel].close
|
93
78
|
end
|
94
|
-
|
95
|
-
def check_errors!(status, query)
|
96
|
-
if (error = ERRORS[status[:code]])
|
97
|
-
raise (
|
98
|
-
already_exists_error(status) ||
|
99
|
-
concurrent_insert_error(status) ||
|
100
|
-
error
|
101
|
-
).new(status, query)
|
102
|
-
end
|
103
|
-
|
104
|
-
return unless SUCCESS[status[:code]].nil?
|
105
|
-
|
106
|
-
raise(UnknownResponseStatus, status)
|
107
|
-
end
|
108
|
-
|
109
|
-
def already_exists_error(status)
|
110
|
-
return VertexAlreadyExistsError if status[:message]&.include?(VERTEX_ALREADY_EXISTS)
|
111
|
-
return EdgeAlreadyExistsError if status[:message]&.include?(EDGE_ALREADY_EXISTS)
|
112
|
-
end
|
113
|
-
|
114
|
-
def concurrent_insert_error(status)
|
115
|
-
return ConcurrentVertexInsertFailedError if status[:message]&.include?(CONCURRENT_VERTEX_INSERT_FAILED)
|
116
|
-
return ConcurrentEdgeInsertFailedError if status[:message]&.include?(CONCURRENT_EDGE_INSERT_FAILED)
|
117
|
-
end
|
118
79
|
end
|
119
80
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
class RequestErrorFactory
|
5
|
+
ERRORS = {
|
6
|
+
499 => InvalidRequestArgumentsError,
|
7
|
+
500 => ServerError,
|
8
|
+
597 => ScriptEvaluationError,
|
9
|
+
599 => ServerSerializationError,
|
10
|
+
598 => ServerTimeoutError,
|
11
|
+
|
12
|
+
401 => ClientSideError,
|
13
|
+
407 => ClientSideError,
|
14
|
+
498 => ClientSideError
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
# Neptune presumably returns message as a JSON string of format
|
18
|
+
# {"detailedMessage":"",
|
19
|
+
# "requestId":"UUID",
|
20
|
+
# "code":"ConcurrentModificationException"}
|
21
|
+
# Currencly we simply search for substings to identify the exact error
|
22
|
+
# TODO: parse json and use `code` instead
|
23
|
+
VERTEX_ALREADY_EXISTS = "Vertex with id already exists:"
|
24
|
+
EDGE_ALREADY_EXISTS = "Edge with id already exists:"
|
25
|
+
CONCURRENT_VERTEX_INSERT_FAILED = "Failed to complete Insert operation for a Vertex due to conflicting concurrent"
|
26
|
+
CONCURRENT_EDGE_INSERT_FAILED = "Failed to complete Insert operation for an Edge due to conflicting concurrent"
|
27
|
+
CONCURRENCT_MODIFICATION_FAILED = "Failed to complete operation due to conflicting concurrent"
|
28
|
+
|
29
|
+
class << self
|
30
|
+
def build(request, response)
|
31
|
+
status = response[:status]
|
32
|
+
query = request[:request]
|
33
|
+
|
34
|
+
if (error = ERRORS[status[:code]])
|
35
|
+
return (
|
36
|
+
already_exists_error(status) ||
|
37
|
+
concurrent_modification_error(status) ||
|
38
|
+
error
|
39
|
+
).new(status, query)
|
40
|
+
end
|
41
|
+
|
42
|
+
return unless RequestDispatcher::SUCCESS[status[:code]].nil?
|
43
|
+
|
44
|
+
UnknownResponseStatus.new(status)
|
45
|
+
end
|
46
|
+
|
47
|
+
def already_exists_error(status)
|
48
|
+
return VertexAlreadyExistsError if status[:message]&.include?(VERTEX_ALREADY_EXISTS)
|
49
|
+
return EdgeAlreadyExistsError if status[:message]&.include?(EDGE_ALREADY_EXISTS)
|
50
|
+
end
|
51
|
+
|
52
|
+
def concurrent_modification_error(status)
|
53
|
+
return ConcurrentVertexInsertFailedError if status[:message]&.include?(CONCURRENT_VERTEX_INSERT_FAILED)
|
54
|
+
return ConcurrentEdgeInsertFailedError if status[:message]&.include?(CONCURRENT_EDGE_INSERT_FAILED)
|
55
|
+
return ConcurrentModificationError if status[:message]&.include?(CONCURRENCT_MODIFICATION_FAILED)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -5,11 +5,12 @@ module Grumlin
|
|
5
5
|
module Properties
|
6
6
|
extend Grumlin::Shortcuts
|
7
7
|
|
8
|
-
shortcut :props do
|
9
|
-
props.reduce(self) do |tt, (prop, value)|
|
10
|
-
next tt
|
8
|
+
shortcut :props do |cardinality = nil, **props|
|
9
|
+
props.reduce(self) do |tt, (prop, value)|
|
10
|
+
next tt if value.nil? # nils are not supported
|
11
|
+
next tt.property(prop, value) if cardinality.nil?
|
11
12
|
|
12
|
-
tt
|
13
|
+
tt.property(cardinality, prop, value)
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -10,8 +10,8 @@ module Grumlin
|
|
10
10
|
.fold
|
11
11
|
.coalesce(
|
12
12
|
__.unfold,
|
13
|
-
__.addV(label).props(**create_properties.merge(T.id => id))
|
14
|
-
).props(**update_properties)
|
13
|
+
__.addV(label).props(Cardinality.single, **create_properties.merge(T.id => id))
|
14
|
+
).props(Cardinality.single, **update_properties)
|
15
15
|
end
|
16
16
|
|
17
17
|
shortcut :upsertE do |label, from, to, create_properties = {}, update_properties = {}|
|
data/lib/grumlin/steppable.rb
CHANGED
@@ -4,13 +4,18 @@ module Grumlin
|
|
4
4
|
class Steppable
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
+
attr_reader :session_id
|
8
|
+
|
7
9
|
START_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
|
8
10
|
REGULAR_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze
|
9
11
|
CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
|
10
12
|
|
11
13
|
ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS
|
12
14
|
|
13
|
-
def initialize
|
15
|
+
def initialize(pool: Grumlin.default_pool, session_id: nil)
|
16
|
+
@pool = pool
|
17
|
+
@session_id = session_id
|
18
|
+
|
14
19
|
return if respond_to?(:shortcuts)
|
15
20
|
|
16
21
|
raise "steppable must not be initialized directly, use Grumlin::Shortcuts::Storage#g or #__ instead"
|
@@ -18,12 +23,12 @@ module Grumlin
|
|
18
23
|
|
19
24
|
ALL_STEPS.each do |step|
|
20
25
|
define_method step do |*args, **params|
|
21
|
-
shortcuts.action_class.new(step, args: args, params: params, previous_step: self)
|
26
|
+
shortcuts.action_class.new(step, args: args, params: params, previous_step: self, session_id: @session_id)
|
22
27
|
end
|
23
28
|
end
|
24
29
|
|
25
30
|
def step(name, *args, **params)
|
26
|
-
shortcuts.action_class.new(name, args: args, params: params, previous_step: self)
|
31
|
+
shortcuts.action_class.new(name, args: args, params: params, previous_step: self, session_id: @session_id)
|
27
32
|
end
|
28
33
|
|
29
34
|
def_delegator :shortcuts, :__
|
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
|
-
|
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
|
14
|
-
|
15
|
-
|
16
|
-
|
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|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
class Transaction
|
5
|
+
attr_reader :session_id
|
6
|
+
|
7
|
+
include Console
|
8
|
+
|
9
|
+
COMMIT = Grumlin::Repository.new.g.step(:tx, :commit).bytecode
|
10
|
+
ROLLBACK = Grumlin::Repository.new.g.step(:tx, :rollback).bytecode
|
11
|
+
|
12
|
+
def initialize(traversal_start_class, pool: Grumlin.default_pool)
|
13
|
+
@traversal_start_class = traversal_start_class
|
14
|
+
@pool = pool
|
15
|
+
|
16
|
+
@session_id = SecureRandom.uuid
|
17
|
+
end
|
18
|
+
|
19
|
+
def begin
|
20
|
+
@traversal_start_class.new(session_id: @session_id)
|
21
|
+
end
|
22
|
+
|
23
|
+
def commit
|
24
|
+
finalize(COMMIT)
|
25
|
+
end
|
26
|
+
|
27
|
+
def rollback
|
28
|
+
finalize(ROLLBACK)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def finalize(action)
|
34
|
+
@pool.acquire do |client|
|
35
|
+
client.write(action, session_id: @session_id)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -4,6 +4,27 @@ module Grumlin
|
|
4
4
|
class TraversalStart < Steppable
|
5
5
|
include WithExtension
|
6
6
|
|
7
|
+
class TraversalError < Grumlin::Error; end
|
8
|
+
class AlreadyBoundToTransactionError < TraversalError; end
|
9
|
+
|
10
|
+
def tx
|
11
|
+
raise AlreadyBoundToTransactionError if @session_id
|
12
|
+
|
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
|
26
|
+
end
|
27
|
+
|
7
28
|
def to_s(*)
|
8
29
|
self.class.to_s
|
9
30
|
end
|
@@ -11,5 +32,11 @@ module Grumlin
|
|
11
32
|
def inspect
|
12
33
|
self.class.inspect
|
13
34
|
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def tx_class
|
39
|
+
Grumlin.features.supports_transactions? ? Transaction : DummyTransaction
|
40
|
+
end
|
14
41
|
end
|
15
42
|
end
|
data/lib/grumlin/version.rb
CHANGED
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
|
@@ -98,7 +101,8 @@ module Grumlin
|
|
98
101
|
class VertexAlreadyExistsError < AlreadyExistsError; end
|
99
102
|
class EdgeAlreadyExistsError < AlreadyExistsError; end
|
100
103
|
|
101
|
-
class
|
104
|
+
class ConcurrentModificationError < ServerError; end
|
105
|
+
class ConcurrentInsertFailedError < ConcurrentModificationError; end
|
102
106
|
|
103
107
|
class ConcurrentVertexInsertFailedError < ConcurrentInsertFailedError; end
|
104
108
|
class ConcurrentEdgeInsertFailedError < ConcurrentInsertFailedError; end
|
@@ -127,27 +131,26 @@ module Grumlin
|
|
127
131
|
|
128
132
|
class WrongQueryResult < RepositoryError; end
|
129
133
|
|
130
|
-
class Config
|
131
|
-
attr_accessor :url, :pool_size, :client_concurrency, :client_factory
|
132
|
-
|
133
|
-
def initialize
|
134
|
-
@pool_size = 10
|
135
|
-
@client_concurrency = 5
|
136
|
-
@client_factory = ->(url, parent) { Grumlin::Client.new(url, parent: parent) }
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
134
|
@pool_mutex = Mutex.new
|
141
135
|
|
142
136
|
class << self
|
143
137
|
def configure
|
144
138
|
yield config
|
139
|
+
|
140
|
+
config.validate!
|
145
141
|
end
|
146
142
|
|
147
143
|
def config
|
148
144
|
@config ||= Config.new
|
149
145
|
end
|
150
146
|
|
147
|
+
# returns a subset of features for currently configured backend.
|
148
|
+
# The features lists are hardcoded as there is no way to get them
|
149
|
+
# from the remote server.
|
150
|
+
def features
|
151
|
+
Features.for(config.provider) # no memoization as provider may be changed
|
152
|
+
end
|
153
|
+
|
151
154
|
def default_pool
|
152
155
|
if Thread.current.thread_variable_get(:grumlin_default_pool)
|
153
156
|
return Thread.current.thread_variable_get(:grumlin_default_pool)
|
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.
|
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-
|
11
|
+
date: 2022-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-pool
|
@@ -96,7 +96,6 @@ files:
|
|
96
96
|
- ".rspec"
|
97
97
|
- ".rubocop.yml"
|
98
98
|
- ".tool-versions"
|
99
|
-
- CHANGELOG.md
|
100
99
|
- CODE_OF_CONDUCT.md
|
101
100
|
- Gemfile
|
102
101
|
- Gemfile.lock
|
@@ -117,6 +116,8 @@ files:
|
|
117
116
|
- lib/grumlin/action.rb
|
118
117
|
- lib/grumlin/benchmark/repository.rb
|
119
118
|
- lib/grumlin/client.rb
|
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
|
@@ -129,12 +130,17 @@ files:
|
|
129
130
|
- lib/grumlin/expressions/t.rb
|
130
131
|
- lib/grumlin/expressions/text_p.rb
|
131
132
|
- lib/grumlin/expressions/with_options.rb
|
133
|
+
- lib/grumlin/features.rb
|
134
|
+
- lib/grumlin/features/feature_list.rb
|
135
|
+
- lib/grumlin/features/neptune_features.rb
|
136
|
+
- lib/grumlin/features/tinkergraph_features.rb
|
132
137
|
- lib/grumlin/path.rb
|
133
138
|
- lib/grumlin/property.rb
|
134
139
|
- lib/grumlin/repository.rb
|
135
140
|
- lib/grumlin/repository/error_handling_strategy.rb
|
136
141
|
- lib/grumlin/repository/instance_methods.rb
|
137
142
|
- lib/grumlin/request_dispatcher.rb
|
143
|
+
- lib/grumlin/request_error_factory.rb
|
138
144
|
- lib/grumlin/shortcut.rb
|
139
145
|
- lib/grumlin/shortcuts.rb
|
140
146
|
- lib/grumlin/shortcuts/properties.rb
|
@@ -152,6 +158,7 @@ files:
|
|
152
158
|
- lib/grumlin/test/rspec.rb
|
153
159
|
- lib/grumlin/test/rspec/db_cleaner_context.rb
|
154
160
|
- lib/grumlin/test/rspec/gremlin_context.rb
|
161
|
+
- lib/grumlin/transaction.rb
|
155
162
|
- lib/grumlin/transport.rb
|
156
163
|
- lib/grumlin/traversal_start.rb
|
157
164
|
- lib/grumlin/traversal_strategies/options_strategy.rb
|
@@ -168,7 +175,7 @@ licenses:
|
|
168
175
|
metadata:
|
169
176
|
homepage_uri: https://github.com/zhulik/grumlin
|
170
177
|
source_code_uri: https://github.com/zhulik/grumlin
|
171
|
-
changelog_uri: https://github.com/
|
178
|
+
changelog_uri: https://github.com/babbel/grumlin/releases
|
172
179
|
rubygems_mfa_required: 'true'
|
173
180
|
post_install_message:
|
174
181
|
rdoc_options: []
|
data/CHANGELOG.md
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
## [0.16.0] - 2022-03-11
|
2
|
-
|
3
|
-
- Query building is rewritten from scratch. No public APIs were changed. [Details](https://github.com/babbel/grumlin/pull/64)
|
4
|
-
- Add support for [TextP](https://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/process/traversal/TextP.html)
|
5
|
-
|
6
|
-
## [0.15.4] - 2022-01-20
|
7
|
-
|
8
|
-
- Move step and expression definitions to a yaml file for better diffs
|
9
|
-
- Add `definitions:format` rake task
|
10
|
-
|
11
|
-
## [0.15.3] - 2022-01-18
|
12
|
-
|
13
|
-
- Fix passing nils as step arguments. Even if they are not supported by the server, they should not be omitted.
|
14
|
-
|
15
|
-
## [0.15.2] - 2022-01-17
|
16
|
-
|
17
|
-
- New steps: `map` and `identity`
|
18
|
-
|
19
|
-
## [0.15.1] - 2022-01-17
|
20
|
-
|
21
|
-
- Fix passing arrays as step arguments
|
22
|
-
|
23
|
-
## [0.15.0] - 2022-01-11
|
24
|
-
|
25
|
-
- Add `properties` step
|
26
|
-
- Add proper support for bulked results
|
27
|
-
- Add support for `Property` objects
|
28
|
-
|
29
|
-
## [0.14.5] - 2021-12-27
|
30
|
-
|
31
|
-
- Fix params handling
|
32
|
-
- Add `aggregate` step
|
33
|
-
- Add `Order.shuffle`
|
34
|
-
|
35
|
-
## [0.14.4] - 2021-12-17
|
36
|
-
|
37
|
-
- `Grumlin::Repository.shorcuts_from` do not raise `ArgumentError` when importing an already existing shortcut
|
38
|
-
pointing to the same block. This fixes importing shortcuts from another repository.
|
39
|
-
|
40
|
-
## [0.14.2] - 2021-12-13
|
41
|
-
|
42
|
-
- Fix `Module` bloating
|
43
|
-
- Add `Operator` expressions
|
44
|
-
- Add `__.coalesce` and `__.constant`
|
45
|
-
- Add steps: `sum`, `sack`
|
46
|
-
- Add configuration steps: `withSack`
|
47
|
-
- Rename `Grumlin::Expressions::Tool` to `Grumlin::Expressions::Expression`
|
48
|
-
|
49
|
-
|
50
|
-
## [0.14.2] - 2021-12-12
|
51
|
-
|
52
|
-
- Better exceptions
|
53
|
-
- Add `choose` step
|
54
|
-
- Add `__.hasNot`, `__.is`, `__.select`
|
55
|
-
|
56
|
-
## [0.14.0] - 2021-12-07
|
57
|
-
|
58
|
-
- Add initial support for [configuration steps](https://tinkerpop.apache.org/docs/current/reference/#configuration-steps)
|
59
|
-
- Add the `withSideEffect` configuration step
|
60
|
-
- Fix passing keyword arguments to regular steps
|
61
|
-
- *Drop support for ruby 2.6*
|
62
|
-
|
63
|
-
## [0.13.0] - 2021-12-03
|
64
|
-
|
65
|
-
- Add `Shortcuts` and `Repository`
|
66
|
-
- Allow executing any gremlin steps by name using `Grumlin::AnonymousStep#step`
|
67
|
-
- Rename `Grumlin::Tools` to `Grumlin::Expressions`
|
68
|
-
|
69
|
-
## [0.1.0] - 2021-05-25
|
70
|
-
|
71
|
-
- Initial release
|