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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +9 -9
  4. data/Gemfile.lock +10 -8
  5. data/README.md +102 -141
  6. data/Rakefile +1 -1
  7. data/bin/console +18 -3
  8. data/doc/middlewares.md +97 -0
  9. data/grumlin.gemspec +1 -0
  10. data/lib/async/channel.rb +54 -56
  11. data/lib/grumlin/benchmark/repository.rb +10 -14
  12. data/lib/grumlin/client.rb +92 -112
  13. data/lib/grumlin/config.rb +30 -15
  14. data/lib/grumlin/dummy_transaction.rb +13 -15
  15. data/lib/grumlin/edge.rb +18 -20
  16. data/lib/grumlin/expressions/cardinality.rb +5 -9
  17. data/lib/grumlin/expressions/column.rb +5 -9
  18. data/lib/grumlin/expressions/expression.rb +7 -11
  19. data/lib/grumlin/expressions/operator.rb +5 -9
  20. data/lib/grumlin/expressions/order.rb +5 -9
  21. data/lib/grumlin/expressions/p.rb +27 -31
  22. data/lib/grumlin/expressions/pop.rb +5 -9
  23. data/lib/grumlin/expressions/scope.rb +5 -9
  24. data/lib/grumlin/expressions/t.rb +5 -9
  25. data/lib/grumlin/expressions/text_p.rb +5 -9
  26. data/lib/grumlin/expressions/with_options.rb +17 -21
  27. data/lib/grumlin/features/feature_list.rb +8 -12
  28. data/lib/grumlin/features/neptune_features.rb +5 -9
  29. data/lib/grumlin/features/tinkergraph_features.rb +5 -9
  30. data/lib/grumlin/features.rb +8 -10
  31. data/lib/grumlin/middlewares/apply_shortcuts.rb +8 -0
  32. data/lib/grumlin/middlewares/build_query.rb +20 -0
  33. data/lib/grumlin/middlewares/builder.rb +15 -0
  34. data/lib/grumlin/middlewares/cast_results.rb +7 -0
  35. data/lib/grumlin/middlewares/find_blocklisted_steps.rb +14 -0
  36. data/lib/grumlin/middlewares/find_mutating_steps.rb +9 -0
  37. data/lib/grumlin/middlewares/middleware.rb +11 -0
  38. data/lib/grumlin/middlewares/run_query.rb +7 -0
  39. data/lib/grumlin/middlewares/serialize_to_bytecode.rb +9 -0
  40. data/lib/grumlin/middlewares/serialize_to_steps.rb +8 -0
  41. data/lib/grumlin/path.rb +11 -13
  42. data/lib/grumlin/property.rb +14 -16
  43. data/lib/grumlin/query_validators/blocklisted_steps_validator.rb +22 -0
  44. data/lib/grumlin/query_validators/validator.rb +36 -0
  45. data/lib/grumlin/repository/error_handling_strategy.rb +36 -40
  46. data/lib/grumlin/repository/instance_methods.rb +115 -118
  47. data/lib/grumlin/repository.rb +82 -58
  48. data/lib/grumlin/request_dispatcher.rb +55 -57
  49. data/lib/grumlin/request_error_factory.rb +53 -55
  50. data/lib/grumlin/shortcut.rb +19 -21
  51. data/lib/grumlin/shortcuts/properties.rb +12 -16
  52. data/lib/grumlin/shortcuts/storage.rb +67 -74
  53. data/lib/grumlin/shortcuts/upserts.rb +18 -22
  54. data/lib/grumlin/shortcuts.rb +23 -25
  55. data/lib/grumlin/shortcuts_applyer.rb +27 -29
  56. data/lib/grumlin/step.rb +92 -0
  57. data/lib/grumlin/step_data.rb +12 -14
  58. data/lib/grumlin/steppable.rb +24 -22
  59. data/lib/grumlin/steps.rb +51 -54
  60. data/lib/grumlin/steps_serializers/bytecode.rb +53 -56
  61. data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +17 -21
  62. data/lib/grumlin/steps_serializers/serializer.rb +7 -11
  63. data/lib/grumlin/steps_serializers/string.rb +26 -30
  64. data/lib/grumlin/test/rspec/db_cleaner_context.rb +8 -12
  65. data/lib/grumlin/test/rspec/gremlin_context.rb +18 -16
  66. data/lib/grumlin/test/rspec.rb +1 -5
  67. data/lib/grumlin/transaction.rb +26 -27
  68. data/lib/grumlin/transport.rb +71 -73
  69. data/lib/grumlin/traversal_start.rb +31 -33
  70. data/lib/grumlin/traversal_strategies/options_strategy.rb +3 -7
  71. data/lib/grumlin/traverser.rb +5 -7
  72. data/lib/grumlin/typed_value.rb +11 -13
  73. data/lib/grumlin/typing.rb +70 -72
  74. data/lib/grumlin/version.rb +1 -1
  75. data/lib/grumlin/vertex.rb +14 -16
  76. data/lib/grumlin/vertex_property.rb +14 -16
  77. data/lib/grumlin/with_extension.rb +17 -19
  78. data/lib/grumlin.rb +23 -19
  79. metadata +32 -6
  80. data/lib/grumlin/action.rb +0 -92
  81. data/lib/grumlin/sugar.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 512af99a3613024d7ea0a4837359fae0f54d0a8cbe1077b5084dc9f5b0136143
4
- data.tar.gz: ef0ba0abdcc269b1873e81b30fd08fce1647b31856f75d6652af67151fc402f4
3
+ metadata.gz: e5a4223c6fde72d3f4ce23f3da0081a816f395a070d8737d9915f15b166c9721
4
+ data.tar.gz: 8c95dcb15d4f55741cc370298e1e763427858908abebaec33f945e404d3a6fa3
5
5
  SHA512:
6
- metadata.gz: 881de04b1163b74f27a6b890d3a43e16deaa2ab05f553c5bb567af20c689dc78fc5fed4cd81a33803ab3ed37f19be721f71e49062bcb89fa318370c5926ed20f
7
- data.tar.gz: e26aad07bc0e3f8f59ca06ed2bb0875b3cc499db7e3982e1617a28da4995a60c8b68dceb6666379fa36289cbe4fa3c7a621c8a72509e7e8fbe7c7f504fcc7ebc
6
+ metadata.gz: 69162f44551c45c0898ffa0c3650cee448a155d1950de5ea0a2a4b570c1b2b41612f3b97bd063f88f683af5fad3c18d4b874b96392a30334c77ab0e1fb1fd226
7
+ data.tar.gz: 1fdb7d10a84da7d8c8173e2b0edb0f5b0f913fb0418fce59bb92d28de9c2e9a1743b89eca1f0ed8495b4d0519d2068c2314e9f17f23b77a4297e080512ace082
data/.gitignore CHANGED
@@ -2,7 +2,6 @@
2
2
  /.yardoc
3
3
  /_yardoc/
4
4
  /coverage/
5
- /doc/
6
5
  /pkg/
7
6
  /spec/reports/
8
7
  /tmp/
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
- # TODO:
88
- # Style/SymbolArray:
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.22.4)
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.1)
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.0)
33
+ async-io (1.34.0)
33
34
  async
34
- async-pool (0.3.11)
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.5)
82
- protocol-http1 (0.14.4)
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.3)
158
- traces (0.6.1)
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` combines functionality of `Grumlin::Sugar` and `Grumlin::Shortcuts` as well as adds a few useful
192
- shortcuts to make gremlin code more rubyish. Can be used as a drop in replacement for `Grumlin::Sugar`. Remember that
193
- `Grumlin::Sugar` needs to be included, but `Grumlin::Repository` - extended. **Classes extending `Grumlin::Repository`
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
- # Repository supports all Grumlin::Shortcut and Grumlin::Sugar features.
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
- An example of how to start an IRB session with support for executing gremlin queries:
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 somehow like
282
+ a custom console class. It should look somewhat like
336
283
 
337
284
  ```ruby
338
- class MyRailsConsole
339
- def self.start
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
- Async do
344
- include Grumlin::Sugar
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
- IRB.setup(binding.source_location[0], argv: [])
347
- workspace = IRB::WorkSpace.new(binding)
308
+ def inspect
309
+ 'main'
310
+ end
348
311
 
349
- IRB::Irb.new(workspace).run(IRB.conf)
350
- ensure
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 sugar and makes sure client is closed after every test
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::Sugar` or `Grumlin::Repository` and not trying to use lower level APIs
382
- as they are subject to change.
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
@@ -9,7 +9,7 @@ require "rubocop/rake_task"
9
9
  RSpec::Core::RakeTask.new(:spec)
10
10
  RuboCop::RakeTask.new
11
11
 
12
- task default: %i[rubocop spec]
12
+ task default: [:rubocop, :spec]
13
13
 
14
14
  namespace :definitions do
15
15
  desc "Format definitions.yml"
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
- Async do
17
- include Grumlin::Sugar
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
- IRB.start
33
+ Async do
34
+ Repository.new.start_irb
20
35
  ensure
21
36
  Grumlin.close
22
37
  end
@@ -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"