grumlin 0.22.4 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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"