grumlin 0.22.4 → 1.0.0.rc1
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/.gitignore +0 -1
- data/.rubocop.yml +9 -9
- data/Gemfile.lock +10 -8
- data/README.md +102 -141
- data/Rakefile +1 -1
- data/bin/console +18 -3
- data/doc/middlewares.md +97 -0
- data/grumlin.gemspec +1 -0
- data/lib/async/channel.rb +54 -56
- data/lib/grumlin/benchmark/repository.rb +10 -14
- data/lib/grumlin/client.rb +92 -112
- data/lib/grumlin/config.rb +30 -15
- data/lib/grumlin/dummy_transaction.rb +13 -15
- data/lib/grumlin/edge.rb +18 -20
- data/lib/grumlin/expressions/cardinality.rb +5 -9
- data/lib/grumlin/expressions/column.rb +5 -9
- data/lib/grumlin/expressions/expression.rb +7 -11
- data/lib/grumlin/expressions/operator.rb +5 -9
- data/lib/grumlin/expressions/order.rb +5 -9
- data/lib/grumlin/expressions/p.rb +27 -31
- data/lib/grumlin/expressions/pop.rb +5 -9
- data/lib/grumlin/expressions/scope.rb +5 -9
- data/lib/grumlin/expressions/t.rb +5 -9
- data/lib/grumlin/expressions/text_p.rb +5 -9
- data/lib/grumlin/expressions/with_options.rb +17 -21
- data/lib/grumlin/features/feature_list.rb +8 -12
- data/lib/grumlin/features/neptune_features.rb +5 -9
- data/lib/grumlin/features/tinkergraph_features.rb +5 -9
- data/lib/grumlin/features.rb +8 -10
- data/lib/grumlin/middlewares/apply_shortcuts.rb +8 -0
- data/lib/grumlin/middlewares/build_query.rb +20 -0
- data/lib/grumlin/middlewares/builder.rb +15 -0
- data/lib/grumlin/middlewares/cast_results.rb +7 -0
- data/lib/grumlin/middlewares/find_blocklisted_steps.rb +14 -0
- data/lib/grumlin/middlewares/find_mutating_steps.rb +9 -0
- data/lib/grumlin/middlewares/middleware.rb +11 -0
- data/lib/grumlin/middlewares/run_query.rb +7 -0
- data/lib/grumlin/middlewares/serialize_to_bytecode.rb +9 -0
- data/lib/grumlin/middlewares/serialize_to_steps.rb +8 -0
- data/lib/grumlin/path.rb +11 -13
- data/lib/grumlin/property.rb +14 -16
- data/lib/grumlin/query_validators/blocklisted_steps_validator.rb +22 -0
- data/lib/grumlin/query_validators/validator.rb +36 -0
- data/lib/grumlin/repository/error_handling_strategy.rb +36 -40
- data/lib/grumlin/repository/instance_methods.rb +115 -118
- data/lib/grumlin/repository.rb +82 -58
- data/lib/grumlin/request_dispatcher.rb +55 -57
- data/lib/grumlin/request_error_factory.rb +53 -55
- data/lib/grumlin/shortcut.rb +19 -21
- data/lib/grumlin/shortcuts/properties.rb +12 -16
- data/lib/grumlin/shortcuts/storage.rb +67 -74
- data/lib/grumlin/shortcuts/upserts.rb +18 -22
- data/lib/grumlin/shortcuts.rb +23 -25
- data/lib/grumlin/shortcuts_applyer.rb +27 -29
- data/lib/grumlin/step.rb +92 -0
- data/lib/grumlin/step_data.rb +12 -14
- data/lib/grumlin/steppable.rb +24 -22
- data/lib/grumlin/steps.rb +51 -54
- data/lib/grumlin/steps_serializers/bytecode.rb +53 -56
- data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +17 -21
- data/lib/grumlin/steps_serializers/serializer.rb +7 -11
- data/lib/grumlin/steps_serializers/string.rb +26 -30
- data/lib/grumlin/test/rspec/db_cleaner_context.rb +8 -12
- data/lib/grumlin/test/rspec/gremlin_context.rb +18 -16
- data/lib/grumlin/test/rspec.rb +1 -5
- data/lib/grumlin/transaction.rb +26 -27
- data/lib/grumlin/transport.rb +71 -73
- data/lib/grumlin/traversal_start.rb +31 -33
- data/lib/grumlin/traversal_strategies/options_strategy.rb +3 -7
- data/lib/grumlin/traverser.rb +5 -7
- data/lib/grumlin/typed_value.rb +11 -13
- data/lib/grumlin/typing.rb +70 -72
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin/vertex.rb +14 -16
- data/lib/grumlin/vertex_property.rb +14 -16
- data/lib/grumlin/with_extension.rb +17 -19
- data/lib/grumlin.rb +23 -19
- metadata +32 -6
- data/lib/grumlin/action.rb +0 -92
- data/lib/grumlin/sugar.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5a4223c6fde72d3f4ce23f3da0081a816f395a070d8737d9915f15b166c9721
|
4
|
+
data.tar.gz: 8c95dcb15d4f55741cc370298e1e763427858908abebaec33f945e404d3a6fa3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69162f44551c45c0898ffa0c3650cee448a155d1950de5ea0a2a4b570c1b2b41612f3b97bd063f88f683af5fad3c18d4b874b96392a30334c77ab0e1fb1fd226
|
7
|
+
data.tar.gz: 1fdb7d10a84da7d8c8173e2b0edb0f5b0f913fb0418fce59bb92d28de9c2e9a1743b89eca1f0ed8495b4d0519d2068c2314e9f17f23b77a4297e080512ace082
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -65,10 +65,6 @@ RSpec/DescribeClass:
|
|
65
65
|
RSpec/MultipleMemoizedHelpers:
|
66
66
|
Max: 7
|
67
67
|
|
68
|
-
Style/WordArray:
|
69
|
-
Exclude:
|
70
|
-
- spec/**/*_spec.rb
|
71
|
-
|
72
68
|
Style/StringLiterals:
|
73
69
|
Enabled: true
|
74
70
|
EnforcedStyle: double_quotes
|
@@ -83,9 +79,13 @@ Style/Documentation:
|
|
83
79
|
Style/MultilineBlockChain:
|
84
80
|
Enabled: false
|
85
81
|
|
82
|
+
Style/SymbolArray:
|
83
|
+
EnforcedStyle: brackets
|
84
|
+
|
85
|
+
Style/WordArray:
|
86
|
+
EnforcedStyle: brackets
|
87
|
+
Exclude:
|
88
|
+
- spec/**/*_spec.rb
|
86
89
|
|
87
|
-
|
88
|
-
|
89
|
-
# EnforcedStyle: brackets
|
90
|
-
# Style/WordArray:
|
91
|
-
# EnforcedStyle: brackets
|
90
|
+
Style/ClassAndModuleChildren:
|
91
|
+
EnforcedStyle: compact
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
grumlin (0.
|
4
|
+
grumlin (1.0.0.rc1)
|
5
5
|
async-pool (~> 0.3)
|
6
6
|
async-websocket (>= 0.19, < 0.20)
|
7
|
+
ibsciss-middleware (~> 0.4.0)
|
7
8
|
oj (~> 3.13)
|
8
9
|
retryable (~> 3.0)
|
9
10
|
zeitwerk (~> 2.6)
|
@@ -21,7 +22,7 @@ GEM
|
|
21
22
|
console (~> 1.10)
|
22
23
|
nio4r (~> 2.3)
|
23
24
|
timers (~> 4.1)
|
24
|
-
async-http (0.59.
|
25
|
+
async-http (0.59.2)
|
25
26
|
async (>= 1.25)
|
26
27
|
async-io (>= 1.28)
|
27
28
|
async-pool (>= 0.2)
|
@@ -29,9 +30,9 @@ GEM
|
|
29
30
|
protocol-http1 (~> 0.14.0)
|
30
31
|
protocol-http2 (~> 0.14.0)
|
31
32
|
traces (>= 0.4.0)
|
32
|
-
async-io (1.
|
33
|
+
async-io (1.34.0)
|
33
34
|
async
|
34
|
-
async-pool (0.3.
|
35
|
+
async-pool (0.3.12)
|
35
36
|
async (>= 1.25)
|
36
37
|
async-rspec (1.16.1)
|
37
38
|
rspec (~> 3.0)
|
@@ -57,6 +58,7 @@ GEM
|
|
57
58
|
fiber-local (1.0.0)
|
58
59
|
i18n (1.12.0)
|
59
60
|
concurrent-ruby (~> 1.0)
|
61
|
+
ibsciss-middleware (0.4.2)
|
60
62
|
iniparse (1.5.0)
|
61
63
|
jaro_winkler (1.5.4)
|
62
64
|
json (2.6.2)
|
@@ -78,8 +80,8 @@ GEM
|
|
78
80
|
parser (3.1.2.1)
|
79
81
|
ast (~> 2.4.1)
|
80
82
|
protocol-hpack (1.4.2)
|
81
|
-
protocol-http (0.23.
|
82
|
-
protocol-http1 (0.14.
|
83
|
+
protocol-http (0.23.12)
|
84
|
+
protocol-http1 (0.14.6)
|
83
85
|
protocol-http (~> 0.22)
|
84
86
|
protocol-http2 (0.14.2)
|
85
87
|
protocol-hpack (~> 1.4)
|
@@ -154,8 +156,8 @@ GEM
|
|
154
156
|
yard (~> 0.9, >= 0.9.24)
|
155
157
|
thor (1.2.1)
|
156
158
|
tilt (2.0.11)
|
157
|
-
timers (4.3.
|
158
|
-
traces (0.
|
159
|
+
timers (4.3.4)
|
160
|
+
traces (0.7.0)
|
159
161
|
tzinfo (2.0.5)
|
160
162
|
concurrent-ruby (~> 1.0)
|
161
163
|
unicode-display_width (2.2.0)
|
data/README.md
CHANGED
@@ -82,116 +82,11 @@ Current differences between providers:
|
|
82
82
|
|
83
83
|
**Warning**: Not all steps and expressions defined in the reference documentation are supported.
|
84
84
|
|
85
|
-
#### Sugar
|
86
|
-
|
87
|
-
Grumlin provides an easy to use module called `Grumlin::Sugar`. Once included in your class it injects some useful
|
88
|
-
constants and methods turning your class into an entrypoint for traversals with pure gremlin experience.
|
89
|
-
|
90
|
-
```ruby
|
91
|
-
class MyRepository
|
92
|
-
include Grumlin::Sugar
|
93
|
-
|
94
|
-
def nodes(property1:, property2:)
|
95
|
-
g.V()
|
96
|
-
.has(T.label, "node")
|
97
|
-
.has(:property1, property1)
|
98
|
-
.has(:property2, property2)
|
99
|
-
.order.by(:property3, Order.asc).limit(10)
|
100
|
-
.toList
|
101
|
-
end
|
102
|
-
end
|
103
|
-
```
|
104
|
-
|
105
|
-
#### Shortcuts
|
106
|
-
|
107
|
-
**Shortcuts** is a way to share and organize gremlin code. They let developers define their own steps consisting of
|
108
|
-
sequences of standard gremlin steps, other shortcuts and even add new initially unsupported by Grumlin steps.
|
109
|
-
Remember ActiveRecord scopes? Shortcuts are very similar.
|
110
|
-
|
111
|
-
**Important**: if a shortcut's name matches a name of a method defined on the wrapped object, this shortcut will be
|
112
|
-
be ignored because methods have higher priority.
|
113
|
-
|
114
|
-
Shortcuts are designed to be used with `Grumlin::Repository` but still can be used separately, with `Grumlin::Sugar`
|
115
|
-
for example.
|
116
|
-
|
117
|
-
**Defining**:
|
118
|
-
```ruby
|
119
|
-
|
120
|
-
# Defining shortcuts
|
121
|
-
class ColorShortcut
|
122
|
-
extend Grumlin::Shortcuts
|
123
|
-
|
124
|
-
# Custom step
|
125
|
-
shortcut :hasColor do |color|
|
126
|
-
has(:color, color)
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
class ChooseShortcut
|
131
|
-
extend Grumlin::Shortcuts
|
132
|
-
|
133
|
-
# Standard Gremlin step
|
134
|
-
shortcut :choose do |*args|
|
135
|
-
step(:choose, *args)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
class AllShortcuts
|
140
|
-
extend Grumlin::Shortcuts
|
141
|
-
|
142
|
-
# Adding shortcuts from other modules
|
143
|
-
shortcuts_from ColorShortcut
|
144
|
-
shortcuts_from ChooseShortcut
|
145
|
-
end
|
146
|
-
```
|
147
|
-
|
148
|
-
**Using with Grumlin::Sugar**:
|
149
|
-
```ruby
|
150
|
-
class MyRepository
|
151
|
-
include Grumlin::Sugar
|
152
|
-
extend Grumlin::Shortcuts
|
153
|
-
|
154
|
-
shortcuts_from AllShortcuts
|
155
|
-
|
156
|
-
# Wrapping a traversal
|
157
|
-
def red_triangles
|
158
|
-
g(self.class.shortcuts).V.hasLabel(:triangle)
|
159
|
-
.hasColor("red")
|
160
|
-
.toList
|
161
|
-
end
|
162
|
-
|
163
|
-
# Wrapping _
|
164
|
-
def something_else
|
165
|
-
g(self.class.shortcuts).V.hasColor("red")
|
166
|
-
.repeat(__(self.class.shortcuts))
|
167
|
-
.out(:has)
|
168
|
-
.hasColor("blue")
|
169
|
-
.toList
|
170
|
-
end
|
171
|
-
end
|
172
|
-
```
|
173
|
-
|
174
|
-
##### Overriding standard steps and shortcuts
|
175
|
-
|
176
|
-
Sometimes it may be useful to override standard steps. Grumlin does not allow it by default, but one
|
177
|
-
is still able to override standard steps if they know what they are doing:
|
178
|
-
|
179
|
-
```ruby
|
180
|
-
shortcut :addV, override: true do |label|
|
181
|
-
super(label).property(:default, :value)
|
182
|
-
end
|
183
|
-
```
|
184
|
-
|
185
|
-
This will create a new shortcut that overrides the standard step `addV` and adds default properties to all vertices
|
186
|
-
created by the repository that uses this shortcut.
|
187
|
-
|
188
|
-
Shortcuts also can be overridden, but super() is not available.
|
189
|
-
|
190
85
|
#### Grumlin::Repository
|
191
|
-
`Grumlin::Repository`
|
192
|
-
|
193
|
-
|
194
|
-
or `Grumlin::Shortcuts` can be inherited**, successors don't need to extend them again and have access to shortcuts
|
86
|
+
`Grumlin::Repository` - is a starting point for all traversals. It provides easy access to `g`, `__` and usual gremlin
|
87
|
+
expressions for you class. It has support for defining your own shortcuts and is even shipped with a couple of useful
|
88
|
+
shortcuts to make gremlin code more rubyish. **Classes extending `Grumlin::Repository`
|
89
|
+
or `Grumlin::Shortcuts` can be inherited**, successors don't need to extend them again and have access to shortcuts
|
195
90
|
defined in the ancestor.
|
196
91
|
|
197
92
|
**Definition**
|
@@ -199,8 +94,8 @@ defined in the ancestor.
|
|
199
94
|
```ruby
|
200
95
|
class MyRepository
|
201
96
|
extend Grumlin::Repository
|
202
|
-
|
203
|
-
|
97
|
+
# read_only! - forbids mutating queries for this repository. May be useful for separation reads and writes
|
98
|
+
|
204
99
|
# It can add shortcuts from another repository or a shortcuts module
|
205
100
|
shortcuts_from ChooseShortcut
|
206
101
|
|
@@ -252,13 +147,13 @@ Each `return_mode` is mapped to a particular termination step:
|
|
252
147
|
- `drop_in_batches(traversal, batch_size: 10_000)`
|
253
148
|
|
254
149
|
and a few methods that emulate upserts:
|
255
|
-
- `upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`
|
150
|
+
- `upsert_vertex(label, id, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`
|
256
151
|
- `upsert_edge(label, from:, to:, create_properties: {}, update_properties: {}, on_failure: :retry, start: g, **params)`
|
257
152
|
- `upsert_edges(edges, batch_size: 100, on_failure: :retry, start: g, **params)`
|
258
153
|
- `upsert_vertices(edges, batch_size: 100, on_failure: :retry, start: g, **params)`
|
259
154
|
|
260
155
|
All of them support 3 different modes for error handling: `:retry`, `:ignore` and `:raise`. Retry mode is implemented
|
261
|
-
with [retryable](https://github.com/nfedyashev/retryable). **params will be merged to the default config for upserts
|
156
|
+
with [retryable](https://github.com/nfedyashev/retryable). **params will be merged to the default config for upserts
|
262
157
|
and passed to `Retryable.retryable`. In case if you want to modify retryable behaviour you are to do so.
|
263
158
|
|
264
159
|
If you want to use these methods inside a transaction simply pass your `gtx` as `start` parameter:
|
@@ -270,7 +165,7 @@ end
|
|
270
165
|
|
271
166
|
If you don't want to define you own repository, simply use
|
272
167
|
|
273
|
-
`Grumlin::Repository.new` returns an instance of an anonymous class extending `Grumlin::Repository`.
|
168
|
+
`Grumlin::Repository.new` returns an instance of an anonymous class extending `Grumlin::Repository`.
|
274
169
|
|
275
170
|
**Usage**
|
276
171
|
|
@@ -295,6 +190,70 @@ it may be useful for debugging. Note that one needs to call a termination step m
|
|
295
190
|
|
296
191
|
method will return profiling data of the results.
|
297
192
|
|
193
|
+
#### Shortcuts
|
194
|
+
|
195
|
+
**Shortcuts** is a way to share and organize gremlin code. They let developers define their own steps consisting of
|
196
|
+
sequences of standard gremlin steps, other shortcuts and even add new initially unsupported by Grumlin steps.
|
197
|
+
Remember ActiveRecord scopes? Shortcuts are very similar.
|
198
|
+
|
199
|
+
**Important**: if a shortcut's name matches a name of a method defined on the wrapped object, this shortcut will be
|
200
|
+
be ignored because methods have higher priority.
|
201
|
+
|
202
|
+
**Defining**:
|
203
|
+
```ruby
|
204
|
+
|
205
|
+
# Defining shortcuts
|
206
|
+
class ColorShortcut
|
207
|
+
extend Grumlin::Shortcuts
|
208
|
+
|
209
|
+
# Custom step
|
210
|
+
shortcut :hasColor do |color|
|
211
|
+
has(:color, color)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class ChooseShortcut
|
216
|
+
extend Grumlin::Shortcuts
|
217
|
+
|
218
|
+
# Standard Gremlin step
|
219
|
+
shortcut :choose do |*args|
|
220
|
+
step(:choose, *args)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
class AllShortcuts
|
225
|
+
extend Grumlin::Shortcuts
|
226
|
+
|
227
|
+
# Adding shortcuts from other modules
|
228
|
+
shortcuts_from ColorShortcut
|
229
|
+
shortcuts_from ChooseShortcut
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
##### Overriding standard steps and shortcuts
|
234
|
+
|
235
|
+
Sometimes it may be useful to override standard steps. Grumlin does not allow it by default, but one
|
236
|
+
is still able to override standard steps if they know what they are doing:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
shortcut :addV, override: true do |label|
|
240
|
+
super(label).property(:default, :value)
|
241
|
+
end
|
242
|
+
```
|
243
|
+
|
244
|
+
This will create a new shortcut that overrides the standard step `addV` and adds default properties to all vertices
|
245
|
+
created by the repository that uses this shortcut.
|
246
|
+
|
247
|
+
Shortcuts also can be overridden, but super() is not available.
|
248
|
+
|
249
|
+
##### Middlewares
|
250
|
+
|
251
|
+
Middlewares can be used to perform certain actions before and after every query made by `Grumlin`. It can be useful for
|
252
|
+
measuring query execution time or performing some modification or validation to the query before it reaches the server or
|
253
|
+
modify the response before client gets it.
|
254
|
+
|
255
|
+
See [doc/middlewares.md](doc/middlewares.md) for more info and examples.
|
256
|
+
|
298
257
|
#### Transactions
|
299
258
|
|
300
259
|
Since 0.22.0 `Grumlin` supports transactions when working with providers that supports them:
|
@@ -315,41 +274,43 @@ end # commits automatically
|
|
315
274
|
|
316
275
|
#### IRB
|
317
276
|
|
318
|
-
|
319
|
-
|
320
|
-
```ruby
|
321
|
-
Async do
|
322
|
-
include Grumlin::Sugar
|
323
|
-
|
324
|
-
IRB.start
|
325
|
-
ensure
|
326
|
-
Grumlin.close
|
327
|
-
end
|
328
|
-
```
|
329
|
-
|
330
|
-
Please check out [bin/console](bin/console) for full source. A similar trick may be applied to PRY.
|
277
|
+
Please check out [bin/console](bin/console) for inspiration. A similar trick may be applied to PRY.
|
331
278
|
|
332
279
|
#### Rails console
|
333
280
|
|
334
281
|
In order to make it possible to execute gremlin queries from the rails console you need to define
|
335
|
-
a custom console class. It should look
|
282
|
+
a custom console class. It should look somewhat like
|
336
283
|
|
337
284
|
```ruby
|
338
|
-
class
|
339
|
-
|
285
|
+
class Async::RailsConsole
|
286
|
+
extend Grumlin::Repository
|
287
|
+
|
288
|
+
def start
|
289
|
+
self.class.shortcuts_from Shortcuts::Content
|
290
|
+
|
340
291
|
IRB::WorkSpace.prepend(Rails::Console::BacktraceCleaner)
|
341
292
|
IRB::ExtendCommandBundle.include(Rails::ConsoleMethods)
|
342
293
|
|
343
|
-
|
344
|
-
|
294
|
+
IRB.setup(binding.source_location[0], argv: [])
|
295
|
+
workspace = IRB::WorkSpace.new(binding)
|
296
|
+
|
297
|
+
begin
|
298
|
+
Async do
|
299
|
+
IRB::Irb.new(workspace).run(IRB.conf)
|
300
|
+
ensure
|
301
|
+
Grumlin.close
|
302
|
+
end
|
303
|
+
rescue StandardError, Interrupt, Async::Stop, IRB::Abort
|
304
|
+
retry
|
305
|
+
end
|
306
|
+
end
|
345
307
|
|
346
|
-
|
347
|
-
|
308
|
+
def inspect
|
309
|
+
'main'
|
310
|
+
end
|
348
311
|
|
349
|
-
|
350
|
-
|
351
|
-
Grumlin.close
|
352
|
-
end
|
312
|
+
def to_s
|
313
|
+
inspect
|
353
314
|
end
|
354
315
|
end
|
355
316
|
```
|
@@ -373,13 +334,13 @@ require 'async/rspec'
|
|
373
334
|
require require "grumlin/test/rspec"
|
374
335
|
...
|
375
336
|
config.include_context(Async::RSpec::Reactor) # Runs async reactor
|
376
|
-
config.include_context(Grumlin::Test::RSpec::GremlinContext) # Injects
|
337
|
+
config.include_context(Grumlin::Test::RSpec::GremlinContext) # Injects `g`, `__` and expressions, makes sure client is closed after every test
|
377
338
|
config.include_context(Grumlin::Test::RSpec::DBCleanerContext) # Cleans the database before every test
|
378
339
|
...
|
379
340
|
```
|
380
341
|
|
381
|
-
It is highly recommended to use `Grumlin::
|
382
|
-
|
342
|
+
It is highly recommended to use `Grumlin::Repository` and not trying to use lower level APIs as they are subject to
|
343
|
+
change.
|
383
344
|
|
384
345
|
## Development
|
385
346
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -13,10 +13,25 @@ Grumlin.configure do |config|
|
|
13
13
|
config.url = ENV.fetch("GREMLIN_URL", "ws://localhost:8182/gremlin")
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
class Repository
|
17
|
+
extend Grumlin::Repository
|
18
|
+
|
19
|
+
def start_irb
|
20
|
+
IRB.setup(nil)
|
21
|
+
IRB.conf[:PROMPT][:DEFAULT] = { PROMPT_I: "%N(main):%03n:%i> ",
|
22
|
+
PROMPT_N: "%N(main):%03n:%i> ",
|
23
|
+
PROMPT_S: "%N(main):%03n:%i%l ",
|
24
|
+
PROMPT_C: "%N(main):%03n:%i* ",
|
25
|
+
RETURN: "=> %s\n" }
|
26
|
+
workspace = IRB::WorkSpace.new(binding)
|
27
|
+
irb = IRB::Irb.new(workspace)
|
28
|
+
IRB.conf[:MAIN_CONTEXT] = irb.context
|
29
|
+
irb.eval_input
|
30
|
+
end
|
31
|
+
end
|
18
32
|
|
19
|
-
|
33
|
+
Async do
|
34
|
+
Repository.new.start_irb
|
20
35
|
ensure
|
21
36
|
Grumlin.close
|
22
37
|
end
|
data/doc/middlewares.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Middlewares
|
2
|
+
|
3
|
+
Every single query performed by `Grumlin` goes through a stack of middlewares just like every single request in Rails
|
4
|
+
or many other web frameworks. `Grumlin` ships with a set of middlewares, each one performs some part of the query
|
5
|
+
execution process:
|
6
|
+
|
7
|
+
- `Middlewares::SerializeToSteps` - converts a `Step` into `Steps`
|
8
|
+
- `Middlewares::ApplyShortcuts` - applies shortcuts to `Steps`
|
9
|
+
- `Middlewares::SerializeToBytecode` - converts `Steps` into bytecode
|
10
|
+
- `Middlewares::BuildQuery` - builds an actual message that will be send to server
|
11
|
+
- `Middlewares::CastResults` - casts server response into ruby objects
|
12
|
+
- `Middlewares::RunQuery` - actually sends the message to the server
|
13
|
+
|
14
|
+
Normally these middlewares must never be rearranged or removed from the stack. Middlewares added after
|
15
|
+
`Middlewares::RunQuery` will not be executed.
|
16
|
+
|
17
|
+
## Writing a middleware for Grumlin
|
18
|
+
|
19
|
+
This entire feature is built on top of [ibsciss-middleware](https://github.com/Ibsciss/ruby-middleware) but uses a
|
20
|
+
slightly modified `Builder` which caches the chain for better performance.
|
21
|
+
Please refer to ibsciss-middleware docs if you want to implement a middleware.
|
22
|
+
|
23
|
+
|
24
|
+
A minimal middleware that measures query execution time and puts it back to `env`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class MeasureExecutionTime < Grumlin::Middlewares::Middleware # Middleware provides only an initializer with one argument for `app`
|
28
|
+
def call(env)
|
29
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
30
|
+
result = @app.call(env)
|
31
|
+
env[:execution_time] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
When placed right before `Grumlin::Middlewares::CastResults` your middleware will have access to every intermediate result
|
38
|
+
of the query execution process:
|
39
|
+
- `env[:traversal]` - contains the original traversal
|
40
|
+
- `env[:steps]` - contains the `Steps` representing the traversal
|
41
|
+
- `env[:steps_without_shortcuts]` - contains the `Steps` representing the traversal, but with all shortcuts applied
|
42
|
+
- `env[:bytecode]` - raw bytecode of the traversal
|
43
|
+
- `env[:query]` - raw message that will be sent to the server. `requestId` can be found here: `env[:query][:requestId]`
|
44
|
+
|
45
|
+
After the query is performed (after `@app.call(env)`), these keys become available:
|
46
|
+
- `env[:results]` - raw results received from the server
|
47
|
+
- `env[:parsed_results]` - server results mapped to ruby types, basically the query results as the client gets it
|
48
|
+
|
49
|
+
Other useful parts of `env`:
|
50
|
+
- `env[:session_id]` - id of the session when executed inside a transaction, otherwise `nil`
|
51
|
+
- `env[:pool]` - connection pool that will be used to interact with the server
|
52
|
+
- `env[:need_results]` - flag stating whether the client needs the query execution results(`toList`, `next`) or not(`iterate`)
|
53
|
+
|
54
|
+
## Adding your middleware to the stack
|
55
|
+
|
56
|
+
There are two ways of adding middlewares to your queries:
|
57
|
+
- `global` - add the middleware to every single query from every single repository
|
58
|
+
- `per repository` - add the middleware to queries performed by particular repositories
|
59
|
+
|
60
|
+
Grumlin has one global middleware stack and every repository defines it's which is by default is a
|
61
|
+
copy of the global stack. This means one can easily modify repository's stack without worrying about the global one.
|
62
|
+
|
63
|
+
When inherited a class extending `Grumlin::Repository` will copy it's middlewares to the subclass, they can also be
|
64
|
+
modified independently from the parent's middlewares.
|
65
|
+
|
66
|
+
### Adding a middleware globally
|
67
|
+
```ruby
|
68
|
+
Grumlin.configure do |cfg|
|
69
|
+
cfg.url = ENV.fetch("GREMLIN_URL", "ws://localhost:8182/gremlin")
|
70
|
+
cfg.provider = :tinkergraph
|
71
|
+
|
72
|
+
cfg.middlewares do |b|
|
73
|
+
b.insert_before Grumlin::Middlewares::CastResults, MeasureExecutionTime
|
74
|
+
end
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
### Adding a middleware to a repository
|
79
|
+
```ruby
|
80
|
+
class MyRepository
|
81
|
+
# MyRepository inherits a copy of global middlewares
|
82
|
+
extend Grumlin::Repository
|
83
|
+
|
84
|
+
# SomeOtherMiddleware will be added to MyRepository's middlewares only
|
85
|
+
middlewares do |b|
|
86
|
+
b.insert_before Grumlin::Middlewares::CastResults, MeasureExecutionTime
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class AnotherRepository < MyRepository
|
91
|
+
# AnotherRepository inherits parent's middlewares
|
92
|
+
middlewares do |b|
|
93
|
+
# SomeOtherMiddleware will be added to AnotherRepository's middlewares only
|
94
|
+
b.insert_before Grumlin::Middlewares::CastResults, SomeOtherMiddleware
|
95
|
+
end
|
96
|
+
end
|
97
|
+
```
|
data/grumlin.gemspec
CHANGED
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
|
|
30
30
|
|
31
31
|
spec.add_dependency "async-pool", "~> 0.3"
|
32
32
|
spec.add_dependency "async-websocket", ">= 0.19", "< 0.20"
|
33
|
+
spec.add_dependency "ibsciss-middleware", "~> 0.4.0"
|
33
34
|
spec.add_dependency "oj", "~> 3.13"
|
34
35
|
spec.add_dependency "retryable", "~> 3.0"
|
35
36
|
spec.add_dependency "zeitwerk", "~> 2.6"
|