grumlin 0.20.1 → 0.21.1
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 +28 -0
- data/gremlin_server/Dockerfile +1 -1
- data/grumlin.gemspec +1 -1
- data/lib/definitions.yml +1 -0
- data/lib/grumlin/action.rb +12 -7
- data/lib/grumlin/client.rb +56 -18
- data/lib/grumlin/config.rb +26 -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 +5 -5
- 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 +9 -5
- data/lib/grumlin/transaction.rb +51 -0
- data/lib/grumlin/traversal_start.rb +11 -0
- data/lib/grumlin/traversal_strategies/options_strategy.rb +11 -0
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin/with_extension.rb +28 -0
- data/lib/grumlin.rb +11 -11
- metadata +12 -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: 95963424bb38728bb21c2d3746a93be60daf303b2c2897b24d99684fd1fb7bff
|
4
|
+
data.tar.gz: 809b4b589b2bc5fd8687f0f63a6f73f17aab82060990b3a5942164c2ac92ef83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b095f3958ebf4b70577e607a0b10e27fc073055fe08d6fe55787c71fd104e77cf78cdcd0a54d06e7881c9183cab479ef6e92e6117a1c1b418945d1ebb417655c
|
7
|
+
data.tar.gz: 0477a8ce8ccfc5ea1cc3b95b7b48501b05387207918478adc4eecb8e286e8495910b6bd718e1d5c00ab63e61e02ca6e89212913ec35d8df49b267cb75281e9eb
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
grumlin (0.
|
4
|
+
grumlin (0.21.1)
|
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,33 @@ 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. Currently there is no difference
|
68
|
+
in behaviour when working with different providers.
|
69
|
+
|
70
|
+
To check current providers supported features use
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
Grumlin.features
|
74
|
+
```
|
75
|
+
|
52
76
|
### Traversing graphs
|
53
77
|
|
54
78
|
**Warning**: Not all steps and expressions defined in the reference documentation are supported.
|
@@ -231,6 +255,10 @@ All of them support 3 different modes for error handling: `:retry`, `:ignore` an
|
|
231
255
|
with [retryable](https://github.com/nfedyashev/retryable). **params will be merged to the default config for upserts
|
232
256
|
and passed to `Retryable.retryable`. In case if you want to modify retryable behaviour you are to do so.
|
233
257
|
|
258
|
+
If you don't want to define you own repository, simply use
|
259
|
+
|
260
|
+
`Grumlin::Repository.new` returns an instance of an anonymous class extending `Grumlin::Repository`.
|
261
|
+
|
234
262
|
**Usage**
|
235
263
|
|
236
264
|
To execute the query defined in a query block one simply needs to call a method with the same name:
|
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/definitions.yml
CHANGED
data/lib/grumlin/action.rb
CHANGED
@@ -4,14 +4,15 @@ 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?
|
@@ -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,14 @@ 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
|
+
ensure
|
30
|
+
@count += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def finalize_tx(action, session_id)
|
34
|
+
@client.finalize_tx(action, session_id)
|
29
35
|
ensure
|
30
36
|
@count += 1
|
31
37
|
end
|
@@ -94,19 +100,19 @@ module Grumlin
|
|
94
100
|
end
|
95
101
|
|
96
102
|
# TODO: support yielding
|
97
|
-
def write(bytecode)
|
103
|
+
def write(bytecode, session_id: nil)
|
98
104
|
raise NotConnectedError unless connected?
|
99
105
|
|
100
|
-
request = to_query(bytecode)
|
101
|
-
|
102
|
-
|
106
|
+
request = to_query(bytecode, session_id: session_id)
|
107
|
+
submit_request(request)
|
108
|
+
end
|
103
109
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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)
|
110
116
|
end
|
111
117
|
|
112
118
|
def inspect
|
@@ -119,20 +125,52 @@ module Grumlin
|
|
119
125
|
|
120
126
|
private
|
121
127
|
|
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
|
+
|
122
140
|
# This might be overridden in successors
|
123
141
|
def build_transport
|
124
142
|
Transport.new(@url, parent: @parent, **@client_options)
|
125
143
|
end
|
126
144
|
|
127
|
-
def to_query(bytecode)
|
145
|
+
def to_query(bytecode, session_id:)
|
146
|
+
{
|
147
|
+
requestId: SecureRandom.uuid,
|
148
|
+
op: :bytecode,
|
149
|
+
processor: session_id ? :session : :traversal,
|
150
|
+
args: {
|
151
|
+
gremlin: {
|
152
|
+
:@type => "g:Bytecode",
|
153
|
+
:@value => bytecode.serialize
|
154
|
+
},
|
155
|
+
aliases: { g: :g },
|
156
|
+
session: session_id
|
157
|
+
}.compact
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def finalize_tx_query(action, session_id)
|
128
162
|
{
|
129
163
|
requestId: SecureRandom.uuid,
|
130
|
-
op:
|
131
|
-
processor:
|
164
|
+
op: :bytecode,
|
165
|
+
processor: session_id ? :session : :traversal,
|
132
166
|
args: {
|
133
|
-
gremlin: {
|
134
|
-
|
135
|
-
|
167
|
+
gremlin: {
|
168
|
+
:@type => "g:Bytecode",
|
169
|
+
:@value => { source: [[:tx, action]] }
|
170
|
+
},
|
171
|
+
aliases: { g: :g },
|
172
|
+
session: session_id
|
173
|
+
}.compact
|
136
174
|
}
|
137
175
|
end
|
138
176
|
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,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 }
|
@@ -56,7 +56,7 @@ module Grumlin
|
|
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).next
|
59
|
+
g.upsertV(label, id, create_properties, update_properties).id.next
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
@@ -70,7 +70,7 @@ module Grumlin
|
|
70
70
|
create_properties, update_properties = cleanup_properties(create_properties, update_properties)
|
71
71
|
|
72
72
|
t.upsertV(label, id, create_properties, update_properties)
|
73
|
-
end.iterate
|
73
|
+
end.id.iterate
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
@@ -80,7 +80,7 @@ module Grumlin
|
|
80
80
|
def upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, **params) # rubocop:disable Metrics/ParameterLists
|
81
81
|
with_upsert_error_handling(on_failure, params) do
|
82
82
|
create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
|
83
|
-
g.upsertE(label, from, to, create_properties, update_properties).next
|
83
|
+
g.upsertE(label, from, to, create_properties, update_properties).id.next
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -94,7 +94,7 @@ module Grumlin
|
|
94
94
|
create_properties, update_properties = cleanup_properties(create_properties, update_properties, T.label)
|
95
95
|
|
96
96
|
t.upsertE(label, from, to, create_properties, update_properties)
|
97
|
-
end.iterate
|
97
|
+
end.id.iterate
|
98
98
|
end
|
99
99
|
end
|
100
100
|
end
|
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,27 +4,31 @@ 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
|
-
raise
|
17
|
-
"steppable must not be initialized directly, use Grumlin::Shortcuts::Storage#g or #__ instead"
|
21
|
+
raise "steppable must not be initialized directly, use Grumlin::Shortcuts::Storage#g or #__ instead"
|
18
22
|
end
|
19
23
|
|
20
24
|
ALL_STEPS.each do |step|
|
21
25
|
define_method step do |*args, **params|
|
22
|
-
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)
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def step(name, *args, **params)
|
27
|
-
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)
|
28
32
|
end
|
29
33
|
|
30
34
|
def_delegator :shortcuts, :__
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
class Transaction
|
5
|
+
attr_reader :uuid
|
6
|
+
|
7
|
+
include Console
|
8
|
+
|
9
|
+
def initialize(traversal_start_class, pool: Grumlin.default_pool)
|
10
|
+
@traversal_start_class = traversal_start_class
|
11
|
+
@pool = pool
|
12
|
+
|
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?
|
25
|
+
end
|
26
|
+
|
27
|
+
def begin
|
28
|
+
@traversal_start_class.new(session_id: @uuid)
|
29
|
+
end
|
30
|
+
|
31
|
+
def commit
|
32
|
+
return unless supported?
|
33
|
+
|
34
|
+
finalize(:commit)
|
35
|
+
end
|
36
|
+
|
37
|
+
def rollback
|
38
|
+
return unless supported?
|
39
|
+
|
40
|
+
finalize(:rollback)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def finalize(action)
|
46
|
+
@pool.acquire do |client|
|
47
|
+
client.finalize_tx(action, @uuid)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
module Grumlin
|
4
4
|
class TraversalStart < Steppable
|
5
|
+
include WithExtension
|
6
|
+
|
7
|
+
class TraversalError < Grumlin::Error; end
|
8
|
+
class AlreadyBoundToTransationError < TraversalError; end
|
9
|
+
|
10
|
+
def tx
|
11
|
+
raise AlreadyBoundToTransationError if @session_id
|
12
|
+
|
13
|
+
Transaction.new(self.class, pool: @pool)
|
14
|
+
end
|
15
|
+
|
5
16
|
def to_s(*)
|
6
17
|
self.class.to_s
|
7
18
|
end
|
data/lib/grumlin/version.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module WithExtension
|
5
|
+
def with(name, value)
|
6
|
+
prev = self
|
7
|
+
strategy = if is_a?(with_action_class)
|
8
|
+
prev = previous_step
|
9
|
+
TraversalStrategies::OptionsStrategy.new(args.first.value.merge(name => value))
|
10
|
+
else
|
11
|
+
TraversalStrategies::OptionsStrategy.new({ name => value })
|
12
|
+
end
|
13
|
+
with_action_class.new(:withStrategies, args: [strategy], previous_step: prev)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def with_action_class
|
19
|
+
@with_action_class ||= Class.new(shortcuts.action_class) do
|
20
|
+
include WithExtension
|
21
|
+
|
22
|
+
def with_action_class
|
23
|
+
self.class
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/grumlin.rb
CHANGED
@@ -98,7 +98,8 @@ module Grumlin
|
|
98
98
|
class VertexAlreadyExistsError < AlreadyExistsError; end
|
99
99
|
class EdgeAlreadyExistsError < AlreadyExistsError; end
|
100
100
|
|
101
|
-
class
|
101
|
+
class ConcurrentModificationError < ServerError; end
|
102
|
+
class ConcurrentInsertFailedError < ConcurrentModificationError; end
|
102
103
|
|
103
104
|
class ConcurrentVertexInsertFailedError < ConcurrentInsertFailedError; end
|
104
105
|
class ConcurrentEdgeInsertFailedError < ConcurrentInsertFailedError; end
|
@@ -127,27 +128,26 @@ module Grumlin
|
|
127
128
|
|
128
129
|
class WrongQueryResult < RepositoryError; end
|
129
130
|
|
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
131
|
@pool_mutex = Mutex.new
|
141
132
|
|
142
133
|
class << self
|
143
134
|
def configure
|
144
135
|
yield config
|
136
|
+
|
137
|
+
config.validate!
|
145
138
|
end
|
146
139
|
|
147
140
|
def config
|
148
141
|
@config ||= Config.new
|
149
142
|
end
|
150
143
|
|
144
|
+
# returns a subset of features for currently configured backend.
|
145
|
+
# The features lists are hardcoded as there is no way to get them
|
146
|
+
# from the remote server.
|
147
|
+
def features
|
148
|
+
Features.for(config.provider) # no memoization as provider may be changed
|
149
|
+
end
|
150
|
+
|
151
151
|
def default_pool
|
152
152
|
if Thread.current.thread_variable_get(:grumlin_default_pool)
|
153
153
|
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.21.1
|
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-11 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,7 @@ 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
120
|
- lib/grumlin/edge.rb
|
121
121
|
- lib/grumlin/expressions/cardinality.rb
|
122
122
|
- lib/grumlin/expressions/column.rb
|
@@ -129,12 +129,17 @@ files:
|
|
129
129
|
- lib/grumlin/expressions/t.rb
|
130
130
|
- lib/grumlin/expressions/text_p.rb
|
131
131
|
- lib/grumlin/expressions/with_options.rb
|
132
|
+
- lib/grumlin/features.rb
|
133
|
+
- lib/grumlin/features/feature_list.rb
|
134
|
+
- lib/grumlin/features/neptune_features.rb
|
135
|
+
- lib/grumlin/features/tinkergraph_features.rb
|
132
136
|
- lib/grumlin/path.rb
|
133
137
|
- lib/grumlin/property.rb
|
134
138
|
- lib/grumlin/repository.rb
|
135
139
|
- lib/grumlin/repository/error_handling_strategy.rb
|
136
140
|
- lib/grumlin/repository/instance_methods.rb
|
137
141
|
- lib/grumlin/request_dispatcher.rb
|
142
|
+
- lib/grumlin/request_error_factory.rb
|
138
143
|
- lib/grumlin/shortcut.rb
|
139
144
|
- lib/grumlin/shortcuts.rb
|
140
145
|
- lib/grumlin/shortcuts/properties.rb
|
@@ -152,13 +157,16 @@ files:
|
|
152
157
|
- lib/grumlin/test/rspec.rb
|
153
158
|
- lib/grumlin/test/rspec/db_cleaner_context.rb
|
154
159
|
- lib/grumlin/test/rspec/gremlin_context.rb
|
160
|
+
- lib/grumlin/transaction.rb
|
155
161
|
- lib/grumlin/transport.rb
|
156
162
|
- lib/grumlin/traversal_start.rb
|
163
|
+
- lib/grumlin/traversal_strategies/options_strategy.rb
|
157
164
|
- lib/grumlin/traverser.rb
|
158
165
|
- lib/grumlin/typed_value.rb
|
159
166
|
- lib/grumlin/typing.rb
|
160
167
|
- lib/grumlin/version.rb
|
161
168
|
- lib/grumlin/vertex.rb
|
169
|
+
- lib/grumlin/with_extension.rb
|
162
170
|
- lib/tasks/benchmark.rake
|
163
171
|
homepage: https://github.com/zhulik/grumlin
|
164
172
|
licenses:
|
@@ -166,7 +174,7 @@ licenses:
|
|
166
174
|
metadata:
|
167
175
|
homepage_uri: https://github.com/zhulik/grumlin
|
168
176
|
source_code_uri: https://github.com/zhulik/grumlin
|
169
|
-
changelog_uri: https://github.com/
|
177
|
+
changelog_uri: https://github.com/babbel/grumlin/releases
|
170
178
|
rubygems_mfa_required: 'true'
|
171
179
|
post_install_message:
|
172
180
|
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
|