grumlin 0.22.5 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 88898067f66c807c685077f768ca71db95d7b246540764334b421f0c86fe92cc
4
- data.tar.gz: 5801ac047063d58395e36e194f961ccb2abce3eb772a0b7648c3f35434700c53
3
+ metadata.gz: 0560f7bf90bba9601628d6042ac1286076f9a8e9039b6f8e755b6f4beedb3e1a
4
+ data.tar.gz: 83b7b4409198a6cbb5520b003e2dcdacfe8623b9aa8c057b0e9df1e29692a4f3
5
5
  SHA512:
6
- metadata.gz: b6377eb1c36a2caf2536f92574d17a57e7da76f8f8cb7995280aabcde1412fc84e214cdb8a296a3d61d6736efd073e09c415b7a34bdf686567b427eb434ca21e
7
- data.tar.gz: 172be358c5882da351e2d12db7688a2521808becf0a027c870fa38b27836e76b82b7a995ddf880f4cc205639dba92dfdc771908450891ac6117f241617969c6a
6
+ metadata.gz: f87b6a69c71ebd867f227b35e13a50c499d054c6688747465e05a41426167ee3ffea5afbfb1875ca5c49e6a0c2a97f42ea20b93f9cf32cd5966942ba844f839c
7
+ data.tar.gz: 4b5262eacf5e57f4ad95e6cc5f450d924858b1e238e2bf3943b9b127da53db36c76078b22d8ba5d244aa35e5d0ddfc4db45516481bd41c58bbe2123ac262dd5f
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/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.22.5)
4
+ grumlin (0.23.0)
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
@@ -295,6 +295,14 @@ it may be useful for debugging. Note that one needs to call a termination step m
295
295
 
296
296
  method will return profiling data of the results.
297
297
 
298
+ ##### Middlewares
299
+
300
+ Middlewares can be used to perform certain actions before and after every query made by `Grumlin`. It can be useful for
301
+ measuring query execution time or performing some modification or validation to the query before it reaches the server or
302
+ modify the response before client gets it.
303
+
304
+ See [doc/middlewares.md](doc/middlewares.md) for more info and examples.
305
+
298
306
  #### Transactions
299
307
 
300
308
  Since 0.22.0 `Grumlin` supports transactions when working with providers that supports them:
@@ -0,0 +1,58 @@
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).
20
+ Please refer to it's docs if you want to implement a middleware.
21
+
22
+
23
+ A minimal middleware that measures query execution time and puts it back to `env`:
24
+
25
+ ```ruby
26
+ class MeasureExecutionTime < Grumlin::Middlewares::Middleware # Middleware provides only an initializer with one argument for `app`
27
+ def call(env)
28
+ started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
29
+ result = @app.call(env)
30
+ env[:execution_time] = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started
31
+ result
32
+ end
33
+ end
34
+
35
+ Grumlin.configure do |cfg|
36
+ cfg.url = ENV.fetch("GREMLIN_URL", "ws://localhost:8182/gremlin")
37
+ cfg.provider = :tinkergraph
38
+
39
+ cfg.middlewares.insert_before Grumlin::Middlewares::CastResults, MeasureExecutionTime
40
+ end
41
+ ```
42
+
43
+ When placed right before `Grumlin::Middlewares::CastResults` your middleware will have access to every intermediate result
44
+ of the query execution process:
45
+ - `env[:traversal]` - contains the original traversal
46
+ - `env[:steps]` - contains the `Steps` representing the traversal
47
+ - `env[:steps_without_shortcuts]` - contains the `Steps` representing the traversal, but with all shortcuts applied
48
+ - `env[:bytecode]` - raw bytecode of the traversal
49
+ - `env[:query]` - raw message that will be sent to the server. `requestId` can be found here: `env[:query][:requestId]`
50
+
51
+ After the query is performed (after `@app.call(env)`), these keys become available:
52
+ - `env[:results]` - raw results received from the server
53
+ - `env[:parsed_results]` - server results mapped to ruby types, basically the query results as the client gets it
54
+
55
+ Other useful parts of `env`:
56
+ - `env[:session_id]` - id of the session when executed inside a transaction, otherwise `nil`
57
+ - `env[:pool]` - connection pool that will be used to interact with the server
58
+ - `env[:need_results]` - flag stating whether the client needs the query execution results(`toList`, `next`) or not(`iterate`)
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"
@@ -24,8 +24,8 @@ module Grumlin
24
24
  @client.close
25
25
  end
26
26
 
27
- def write(bytecode, session_id: nil)
28
- @client.write(bytecode, session_id: session_id)
27
+ def write(query)
28
+ @client.write(query)
29
29
  ensure
30
30
  @count += 1
31
31
  end
@@ -94,15 +94,13 @@ module Grumlin
94
94
  end
95
95
 
96
96
  # TODO: support yielding
97
- def write(bytecode, session_id: nil)
97
+ def write(query)
98
98
  raise NotConnectedError unless connected?
99
99
 
100
- request = to_query(bytecode, session_id: session_id)
101
-
102
- channel = @request_dispatcher.add_request(request)
100
+ channel = @request_dispatcher.add_request(query)
103
101
  begin
104
- @transport.write(request)
105
- channel.dequeue.flat_map { |item| Typing.cast(item) }
102
+ @transport.write(query)
103
+ channel.dequeue
106
104
  rescue Async::Stop, Async::TimeoutError
107
105
  close(check_requests: false)
108
106
  raise
@@ -123,21 +121,5 @@ module Grumlin
123
121
  def build_transport
124
122
  Transport.new(@url, parent: @parent, **@client_options)
125
123
  end
126
-
127
- def to_query(bytecode, session_id:)
128
- {
129
- requestId: SecureRandom.uuid,
130
- op: :bytecode,
131
- processor: session_id ? :session : :traversal,
132
- args: {
133
- gremlin: {
134
- :@type => "g:Bytecode",
135
- :@value => bytecode.serialize
136
- },
137
- aliases: { g: :g },
138
- session: session_id
139
- }.compact
140
- }
141
- end
142
124
  end
143
125
  end
@@ -6,6 +6,15 @@ module Grumlin
6
6
 
7
7
  SUPPORTED_PROVIDERS = %i[neptune tinkergraph].freeze
8
8
 
9
+ DEFAULT_MIDDLEWARES = Middleware::Builder.new do |b|
10
+ b.use Middlewares::SerializeToSteps
11
+ b.use Middlewares::ApplyShortcuts
12
+ b.use Middlewares::SerializeToBytecode
13
+ b.use Middlewares::BuildQuery
14
+ b.use Middlewares::CastResults
15
+ b.use Middlewares::RunQuery
16
+ end.freeze
17
+
9
18
  class ConfigurationError < Grumlin::Error; end
10
19
 
11
20
  class UnknownProviderError < ConfigurationError; end
@@ -17,6 +26,12 @@ module Grumlin
17
26
  @client_factory = ->(url, parent) { Grumlin::Client.new(url, parent: parent) }
18
27
  end
19
28
 
29
+ def middlewares
30
+ @middlewares ||= Middleware::Builder.new do |b|
31
+ b.use DEFAULT_MIDDLEWARES
32
+ end
33
+ end
34
+
20
35
  def validate!
21
36
  return if SUPPORTED_PROVIDERS.include?(provider.to_sym)
22
37
 
@@ -6,7 +6,7 @@ module Grumlin
6
6
 
7
7
  include Console
8
8
 
9
- def initialize(traversal_start_class, pool: nil) # rubocop:disable Lint/MissingSuper, Lint/UnusedMethodArgument
9
+ def initialize(traversal_start_class, middlewares:, pool: nil) # rubocop:disable Lint/MissingSuper, Lint/UnusedMethodArgument
10
10
  @traversal_start_class = traversal_start_class
11
11
 
12
12
  logger.info(self) do
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class ApplyShortcuts < Middleware
6
+ def call(env)
7
+ env[:steps_without_shortcuts] = ShortcutsApplyer.call(env[:steps])
8
+ @app.call(env)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class BuildQuery < Middleware
6
+ def call(env)
7
+ env[:query] = {
8
+ requestId: SecureRandom.uuid,
9
+ op: :bytecode,
10
+ processor: env[:session_id] ? :session : :traversal,
11
+ args: {
12
+ gremlin: {
13
+ :@type => "g:Bytecode",
14
+ :@value => env[:bytecode]
15
+ },
16
+ aliases: { g: :g },
17
+ session: env[:session_id]
18
+ }.compact
19
+ }
20
+ @app.call(env)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class CastResults < Middleware
6
+ def call(env)
7
+ env[:parsed_results] = @app.call(env).flat_map { |item| Typing.cast(item) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class FrozenBuilder < ::Middleware::Builder
6
+ def initialize(opts = nil, &block)
7
+ super(opts, &block)
8
+ freeze
9
+ end
10
+
11
+ def freeze
12
+ super
13
+
14
+ stack.freeze
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class Middleware
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ raise NotImplementedError
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class RunQuery < Middleware
6
+ def call(env)
7
+ env[:results] = env[:pool].acquire { |c| c.write(env[:query]) }
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class SerializeToBytecode < Middleware
6
+ def call(env)
7
+ env[:bytecode] = StepsSerializers::Bytecode.new(env[:steps_without_shortcuts],
8
+ no_return: !env[:need_results]).serialize
9
+ @app.call(env)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Middlewares
5
+ class SerializeToSteps < Middleware
6
+ def call(env)
7
+ env[:steps] = Steps.from(env[:traversal])
8
+ @app.call(env)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -32,9 +32,9 @@ module Grumlin
32
32
  t = instance_exec(*args, **params, &query_block)
33
33
  return t if t.nil? || (t.respond_to?(:empty?) && t.empty?)
34
34
 
35
- unless t.is_a?(Grumlin::Action)
35
+ unless t.is_a?(Grumlin::Step)
36
36
  raise WrongQueryResult,
37
- "queries must return #{Grumlin::Action}, nil or an empty collection. Given: #{t.class}"
37
+ "queries must return #{Grumlin::Step}, nil or an empty collection. Given: #{t.class}"
38
38
  end
39
39
 
40
40
  return block.call(t) unless block.nil?
@@ -33,10 +33,10 @@ module Grumlin
33
33
  def add(name, shortcut)
34
34
  @storage[name] = shortcut
35
35
 
36
- ac = action_class
36
+ sc = step_class
37
37
 
38
38
  shortcut_methods_module.define_method(name) do |*args, **params|
39
- next ac.new(name, args: args, params: params, previous_step: self, pool: Grumlin.default_pool)
39
+ next sc.new(name, args: args, params: params, previous_step: self, pool: Grumlin.default_pool)
40
40
  end
41
41
  extend_traversal_classes(shortcut) unless shortcut.lazy?
42
42
  end
@@ -59,8 +59,8 @@ module Grumlin
59
59
  @traversal_start_class ||= shortcut_aware_class(TraversalStart)
60
60
  end
61
61
 
62
- def action_class
63
- @action_class ||= shortcut_aware_class(Action)
62
+ def step_class
63
+ @step_class ||= shortcut_aware_class(Step)
64
64
  end
65
65
 
66
66
  protected
@@ -91,7 +91,7 @@ module Grumlin
91
91
  m = Module.new do
92
92
  define_method(shortcut.name, &shortcut.block)
93
93
  end
94
- action_class.include(m)
94
+ step_class.include(m)
95
95
  traversal_start_class.include(m)
96
96
  end
97
97
  end
@@ -15,7 +15,7 @@ module Grumlin
15
15
  name = name.to_sym
16
16
  lazy = false if override
17
17
 
18
- if Grumlin::Action::REGULAR_STEPS.include?(name) && !override
18
+ if Grumlin::Step::REGULAR_STEPS.include?(name) && !override
19
19
  raise ArgumentError,
20
20
  "overriding standard gremlin steps is not allowed, if you know what you're doing, pass `override: true`"
21
21
  end
@@ -30,10 +30,10 @@ module Grumlin
30
30
  next result << StepData.new(step.name, args: args, params: step.params) unless shortcut&.lazy?
31
31
 
32
32
  t = shortcuts.__
33
- action = shortcut.apply(t, *args, **step.params)
34
- next if action.nil? || action == t # Shortcut did not add any steps
33
+ step = shortcut.apply(t, *args, **step.params)
34
+ next if step.nil? || step == t # Shortcut did not add any steps
35
35
 
36
- new_steps = call(Steps.from(action))
36
+ new_steps = call(Steps.from(step))
37
37
  result.concat(new_steps.configuration_steps, new_steps.steps)
38
38
  end
39
39
  end
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- class Action < Steppable
4
+ class Step < Steppable
5
5
  attr_reader :name, :args, :params, :next_step, :configuration_steps, :previous_step, :shortcut
6
6
 
7
- # client is only used when a traversal is a part of transaction
8
- def initialize(name, args: [], params: {}, previous_step: nil, pool: nil, session_id: nil)
9
- super(pool: pool, session_id: session_id)
7
+ # TODO: replace pool, session_id and middlewares with a context?
8
+ def initialize(name, args: [], params: {}, previous_step: nil, pool: nil, session_id: nil, # rubocop:disable Metrics/ParameterLists
9
+ middlewares: Grumlin.default_middlewares)
10
+ super(pool: pool, session_id: session_id, middlewares: middlewares)
10
11
 
11
12
  @name = name.to_sym
12
- @args = args # TODO: add recursive validation: only json types or Action
13
+ @args = args # TODO: add recursive validation: only json types or Step
13
14
  @params = params # TODO: add recursive validation: only json types
14
15
  @previous_step = previous_step
15
16
  @shortcut = shortcuts[@name]
@@ -74,19 +75,20 @@ module Grumlin
74
75
  end
75
76
 
76
77
  def toList
77
- client_write(bytecode)
78
+ send_query(need_results: true)
78
79
  end
79
80
 
80
81
  def iterate
81
- client_write(bytecode(no_return: true))
82
+ send_query(need_results: false)
82
83
  end
83
84
 
84
85
  private
85
86
 
86
- def client_write(payload)
87
- @pool.acquire do |client|
88
- client.write(payload, session_id: @session_id)
89
- end
87
+ def send_query(need_results:)
88
+ @middlewares.call(traversal: self,
89
+ need_results: need_results,
90
+ session_id: @session_id,
91
+ pool: @pool)
90
92
  end
91
93
  end
92
94
  end
@@ -12,10 +12,12 @@ module Grumlin
12
12
 
13
13
  ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS
14
14
 
15
- def initialize(pool: nil, session_id: nil)
15
+ def initialize(pool: nil, session_id: nil, middlewares: Grumlin.default_middlewares)
16
16
  @pool = pool
17
17
  @session_id = session_id
18
18
 
19
+ @middlewares = middlewares
20
+
19
21
  return if respond_to?(:shortcuts)
20
22
 
21
23
  raise "steppable must not be initialized directly, use Grumlin::Shortcuts::Storage#g or #__ instead"
@@ -23,14 +25,14 @@ module Grumlin
23
25
 
24
26
  ALL_STEPS.each do |step|
25
27
  define_method step do |*args, **params|
26
- shortcuts.action_class.new(step, args: args, params: params, previous_step: self,
27
- session_id: @session_id, pool: @pool)
28
+ shortcuts.step_class.new(step, args: args, params: params, previous_step: self,
29
+ session_id: @session_id, pool: @pool, middlewares: @middlewares)
28
30
  end
29
31
  end
30
32
 
31
33
  def step(name, *args, **params)
32
- shortcuts.action_class.new(name, args: args, params: params, previous_step: self,
33
- session_id: @session_id, pool: @pool)
34
+ shortcuts.step_class.new(name, args: args, params: params, previous_step: self,
35
+ session_id: @session_id, pool: @pool, middlewares: @middlewares)
34
36
  end
35
37
 
36
38
  def_delegator :shortcuts, :__
data/lib/grumlin/steps.rb CHANGED
@@ -2,23 +2,16 @@
2
2
 
3
3
  module Grumlin
4
4
  class Steps
5
- CONFIGURATION_STEPS = Action::CONFIGURATION_STEPS
6
- ALL_STEPS = Action::ALL_STEPS
5
+ CONFIGURATION_STEPS = Step::CONFIGURATION_STEPS
6
+ ALL_STEPS = Step::ALL_STEPS
7
7
 
8
- def self.from(action)
9
- raise ArgumentError, "expected: #{Action}, given: #{action.class}" unless action.is_a?(Action)
8
+ def self.from(step)
9
+ raise ArgumentError, "expected: #{Step}, given: #{step.class}" unless step.is_a?(Step)
10
10
 
11
- shortcuts = action.shortcuts
12
- actions = []
13
-
14
- until action.nil? || action.is_a?(TraversalStart)
15
- actions.unshift(action)
16
- action = action.previous_step
17
- end
18
-
19
- new(shortcuts).tap do |chain|
20
- actions.each do |act|
21
- chain.add(act.name, args: act.args, params: act.params)
11
+ new(step.shortcuts).tap do |chain|
12
+ until step.nil? || step.is_a?(TraversalStart)
13
+ chain.add(step.name, args: step.args, params: step.params, to: :begin)
14
+ step = step.previous_step
22
15
  end
23
16
  end
24
17
  end
@@ -31,13 +24,16 @@ module Grumlin
31
24
  @steps = steps
32
25
  end
33
26
 
34
- def add(name, args: [], params: {})
27
+ def add(name, args: [], params: {}, to: :end)
35
28
  if CONFIGURATION_STEPS.include?(name) || name.to_sym == :tx
36
- return add_configuration_step(name, args: args, params: params)
29
+ return add_configuration_step(name, args: args, params: params, to: to)
37
30
  end
38
31
 
39
32
  StepData.new(name, args: cast_arguments(args), params: params).tap do |step|
40
- @steps << step
33
+ next @steps << step if to == :end
34
+ next @steps.unshift(step) if to == :begin
35
+
36
+ raise ArgumentError, "'to:' must be either :begin or :end, given: '#{to}'"
41
37
  end
42
38
  end
43
39
 
@@ -64,16 +60,19 @@ module Grumlin
64
60
  end
65
61
  end
66
62
 
67
- def add_configuration_step(name, args: [], params: {})
68
- raise ArgumentError, "cannot use configuration steps after start step was used" unless @steps.empty?
63
+ def add_configuration_step(name, args: [], params: {}, to: :end)
64
+ raise ArgumentError, "cannot use configuration steps after start step was used" if @steps.any? && to == :end
69
65
 
70
66
  StepData.new(name, args: cast_arguments(args), params: params).tap do |step|
71
- @configuration_steps << step
67
+ next @configuration_steps << step if to == :end
68
+ next @configuration_steps.unshift(step) if to == :begin
69
+
70
+ raise ArgumentError, "to must be either :begin or :end, given: '#{to}'"
72
71
  end
73
72
  end
74
73
 
75
74
  def cast_arguments(arguments)
76
- arguments.map { |arg| arg.is_a?(Action) ? Steps.from(arg) : arg }
75
+ arguments.map { |arg| arg.is_a?(Step) ? Steps.from(arg) : arg }
77
76
  end
78
77
  end
79
78
  end
@@ -6,14 +6,14 @@ module Grumlin
6
6
 
7
7
  include Console
8
8
 
9
- COMMIT = Grumlin::Repository.new.g.step(:tx, :commit).bytecode
10
- ROLLBACK = Grumlin::Repository.new.g.step(:tx, :rollback).bytecode
9
+ COMMIT = Grumlin::Repository.new.g.step(:tx, :commit)
10
+ ROLLBACK = Grumlin::Repository.new.g.step(:tx, :rollback)
11
11
 
12
- def initialize(traversal_start_class, pool:)
12
+ def initialize(traversal_start_class, pool:, middlewares:)
13
13
  @traversal_start_class = traversal_start_class
14
14
  @pool = pool
15
-
16
15
  @session_id = SecureRandom.uuid
16
+ @middlewares = middlewares
17
17
  end
18
18
 
19
19
  def begin
@@ -30,10 +30,11 @@ module Grumlin
30
30
 
31
31
  private
32
32
 
33
- def finalize(action)
34
- @pool.acquire do |client|
35
- client.write(action, session_id: @session_id)
36
- end
33
+ def finalize(step)
34
+ @middlewares.call(traversal: step,
35
+ need_results: false,
36
+ session_id: @session_id,
37
+ pool: @pool)
37
38
  end
38
39
  end
39
40
  end
@@ -10,7 +10,7 @@ module Grumlin
10
10
  def tx
11
11
  raise AlreadyBoundToTransactionError if @session_id
12
12
 
13
- transaction = tx_class.new(self.class, pool: @pool)
13
+ transaction = tx_class.new(self.class, pool: @pool, middlewares: @middlewares)
14
14
  return transaction unless block_given?
15
15
 
16
16
  begin
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.22.5"
4
+ VERSION = "0.23.0"
5
5
  end
@@ -4,22 +4,22 @@ module Grumlin
4
4
  module WithExtension
5
5
  def with(name, value)
6
6
  prev = self
7
- strategy = if is_a?(with_action_class)
7
+ strategy = if is_a?(with_step_class)
8
8
  prev = previous_step
9
9
  TraversalStrategies::OptionsStrategy.new(args.first.value.merge(name => value))
10
10
  else
11
11
  TraversalStrategies::OptionsStrategy.new({ name => value })
12
12
  end
13
- with_action_class.new(:withStrategies, args: [strategy], previous_step: prev)
13
+ with_step_class.new(:withStrategies, args: [strategy], previous_step: prev)
14
14
  end
15
15
 
16
16
  private
17
17
 
18
- def with_action_class
19
- @with_action_class ||= Class.new(shortcuts.action_class) do
18
+ def with_step_class
19
+ @with_step_class ||= Class.new(shortcuts.step_class) do
20
20
  include WithExtension
21
21
 
22
- def with_action_class
22
+ def with_step_class
23
23
  self.class
24
24
  end
25
25
  end
data/lib/grumlin.rb CHANGED
@@ -17,6 +17,7 @@ require "async/barrier"
17
17
  require "async/http/endpoint"
18
18
  require "async/websocket/client"
19
19
 
20
+ require "middleware"
20
21
  require "retryable"
21
22
 
22
23
  require "zeitwerk"
@@ -34,6 +35,7 @@ module Grumlin
34
35
  class Error < StandardError; end
35
36
 
36
37
  class TransactionError < Error; end
38
+
37
39
  class Rollback < TransactionError; end
38
40
 
39
41
  class UnknownError < Error; end
@@ -99,15 +101,19 @@ module Grumlin
99
101
  end
100
102
 
101
103
  class VertexAlreadyExistsError < AlreadyExistsError; end
104
+
102
105
  class EdgeAlreadyExistsError < AlreadyExistsError; end
103
106
 
104
107
  class ConcurrentModificationError < ServerError; end
108
+
105
109
  class ConcurrentInsertFailedError < ConcurrentModificationError; end
106
110
 
107
111
  class ConcurrentVertexInsertFailedError < ConcurrentInsertFailedError; end
112
+
108
113
  class ConcurrentEdgeInsertFailedError < ConcurrentInsertFailedError; end
109
114
 
110
115
  class ConcurrentVertexPropertyInsertFailedError < ConcurrentInsertFailedError; end
116
+
111
117
  class ConcurrentEdgePropertyInsertFailedError < ConcurrentInsertFailedError; end
112
118
 
113
119
  class ServerSerializationError < ServerSideError; end
@@ -134,8 +140,6 @@ module Grumlin
134
140
 
135
141
  class WrongQueryResult < RepositoryError; end
136
142
 
137
- @pool_mutex = Mutex.new
138
-
139
143
  class << self
140
144
  def configure
141
145
  yield config
@@ -147,6 +151,10 @@ module Grumlin
147
151
  @config ||= Config.new
148
152
  end
149
153
 
154
+ def default_middlewares
155
+ config.middlewares
156
+ end
157
+
150
158
  # returns a subset of features for currently configured backend.
151
159
  # The features lists are hardcoded as there is no way to get them
152
160
  # from the remote server.
@@ -155,26 +163,22 @@ module Grumlin
155
163
  end
156
164
 
157
165
  def default_pool
158
- if Thread.current.thread_variable_get(:grumlin_default_pool)
159
- return Thread.current.thread_variable_get(:grumlin_default_pool)
160
- end
161
-
162
- @pool_mutex.synchronize do
163
- Thread.current.thread_variable_set(:grumlin_default_pool,
164
- Async::Pool::Controller.new(Grumlin::Client::PoolResource,
165
- limit: config.pool_size))
166
- end
166
+ t = Thread.current
167
+ return t.thread_variable_get(:grumlin_default_pool) if t.thread_variable_get(:grumlin_default_pool)
168
+
169
+ t.thread_variable_set(:grumlin_default_pool,
170
+ Async::Pool::Controller.new(Grumlin::Client::PoolResource,
171
+ limit: config.pool_size))
167
172
  end
168
173
 
169
174
  def close
170
- return if Thread.current.thread_variable_get(:grumlin_default_pool).nil?
171
-
172
- @pool_mutex.synchronize do
173
- pool = Thread.current.thread_variable_get(:grumlin_default_pool)
174
- pool.wait while pool.busy?
175
- pool.close
176
- Thread.current.thread_variable_set(:grumlin_default_pool, nil)
177
- end
175
+ t = Thread.current
176
+ return if t.thread_variable_get(:grumlin_default_pool).nil?
177
+
178
+ pool = t.thread_variable_get(:grumlin_default_pool)
179
+ pool.wait while pool.busy?
180
+ pool.close
181
+ t.thread_variable_set(:grumlin_default_pool, nil)
178
182
  end
179
183
 
180
184
  def definitions
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.22.5
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-25 00:00:00.000000000 Z
11
+ date: 2022-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool
@@ -44,6 +44,20 @@ dependencies:
44
44
  - - "<"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0.20'
47
+ - !ruby/object:Gem::Dependency
48
+ name: ibsciss-middleware
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.4.0
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.4.0
47
61
  - !ruby/object:Gem::Dependency
48
62
  name: oj
49
63
  requirement: !ruby/object:Gem::Requirement
@@ -110,6 +124,7 @@ files:
110
124
  - Rakefile
111
125
  - bin/console
112
126
  - bin/setup
127
+ - doc/middlewares.md
113
128
  - docker-compose.yml
114
129
  - gremlin_server/Dockerfile
115
130
  - gremlin_server/tinkergraph-empty.properties
@@ -119,7 +134,6 @@ files:
119
134
  - lib/async/channel.rb
120
135
  - lib/definitions.yml
121
136
  - lib/grumlin.rb
122
- - lib/grumlin/action.rb
123
137
  - lib/grumlin/benchmark/repository.rb
124
138
  - lib/grumlin/client.rb
125
139
  - lib/grumlin/config.rb
@@ -140,6 +154,14 @@ files:
140
154
  - lib/grumlin/features/feature_list.rb
141
155
  - lib/grumlin/features/neptune_features.rb
142
156
  - lib/grumlin/features/tinkergraph_features.rb
157
+ - lib/grumlin/middlewares/apply_shortcuts.rb
158
+ - lib/grumlin/middlewares/build_query.rb
159
+ - lib/grumlin/middlewares/cast_results.rb
160
+ - lib/grumlin/middlewares/frozen_builder.rb
161
+ - lib/grumlin/middlewares/middleware.rb
162
+ - lib/grumlin/middlewares/run_query.rb
163
+ - lib/grumlin/middlewares/serialize_to_bytecode.rb
164
+ - lib/grumlin/middlewares/serialize_to_steps.rb
143
165
  - lib/grumlin/path.rb
144
166
  - lib/grumlin/property.rb
145
167
  - lib/grumlin/repository.rb
@@ -153,6 +175,7 @@ files:
153
175
  - lib/grumlin/shortcuts/storage.rb
154
176
  - lib/grumlin/shortcuts/upserts.rb
155
177
  - lib/grumlin/shortcuts_applyer.rb
178
+ - lib/grumlin/step.rb
156
179
  - lib/grumlin/step_data.rb
157
180
  - lib/grumlin/steppable.rb
158
181
  - lib/grumlin/steps.rb