grumlin 0.22.5 → 0.23.0

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