grumlin 0.23.0 → 1.0.0.rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -9
- data/Gemfile.lock +1 -1
- data/README.md +100 -142
- data/Rakefile +1 -1
- data/bin/console +18 -3
- data/doc/middlewares.md +49 -10
- data/lib/async/channel.rb +54 -56
- data/lib/grumlin/benchmark/repository.rb +10 -14
- data/lib/grumlin/client.rb +93 -95
- data/lib/grumlin/config.rb +33 -33
- data/lib/grumlin/dummy_transaction.rb +13 -15
- data/lib/grumlin/edge.rb +18 -20
- data/lib/grumlin/expressions/cardinality.rb +5 -9
- data/lib/grumlin/expressions/column.rb +5 -9
- data/lib/grumlin/expressions/expression.rb +7 -11
- data/lib/grumlin/expressions/operator.rb +5 -9
- data/lib/grumlin/expressions/order.rb +5 -9
- data/lib/grumlin/expressions/p.rb +27 -31
- data/lib/grumlin/expressions/pop.rb +5 -9
- data/lib/grumlin/expressions/scope.rb +5 -9
- data/lib/grumlin/expressions/t.rb +5 -9
- data/lib/grumlin/expressions/text_p.rb +5 -9
- data/lib/grumlin/expressions/with_options.rb +17 -21
- data/lib/grumlin/features/feature_list.rb +8 -12
- data/lib/grumlin/features/neptune_features.rb +5 -9
- data/lib/grumlin/features/tinkergraph_features.rb +5 -9
- data/lib/grumlin/features.rb +8 -10
- data/lib/grumlin/middlewares/apply_shortcuts.rb +4 -8
- data/lib/grumlin/middlewares/build_query.rb +16 -20
- data/lib/grumlin/middlewares/builder.rb +15 -0
- data/lib/grumlin/middlewares/cast_results.rb +3 -7
- data/lib/grumlin/middlewares/find_blocklisted_steps.rb +14 -0
- data/lib/grumlin/middlewares/find_mutating_steps.rb +9 -0
- data/lib/grumlin/middlewares/middleware.rb +6 -10
- data/lib/grumlin/middlewares/run_query.rb +3 -7
- data/lib/grumlin/middlewares/serialize_to_bytecode.rb +5 -9
- data/lib/grumlin/middlewares/serialize_to_steps.rb +4 -8
- data/lib/grumlin/path.rb +11 -13
- data/lib/grumlin/property.rb +14 -16
- data/lib/grumlin/query_validators/blocklisted_steps_validator.rb +22 -0
- data/lib/grumlin/query_validators/validator.rb +36 -0
- data/lib/grumlin/repository/error_handling_strategy.rb +36 -40
- data/lib/grumlin/repository/instance_methods.rb +115 -118
- data/lib/grumlin/repository.rb +82 -58
- data/lib/grumlin/request_dispatcher.rb +55 -57
- data/lib/grumlin/request_error_factory.rb +53 -55
- data/lib/grumlin/shortcut.rb +19 -21
- data/lib/grumlin/shortcuts/properties.rb +12 -16
- data/lib/grumlin/shortcuts/storage.rb +67 -74
- data/lib/grumlin/shortcuts/upserts.rb +19 -22
- data/lib/grumlin/shortcuts.rb +23 -25
- data/lib/grumlin/shortcuts_applyer.rb +27 -29
- data/lib/grumlin/step.rb +88 -90
- data/lib/grumlin/step_data.rb +12 -14
- data/lib/grumlin/steppable.rb +23 -25
- data/lib/grumlin/steps.rb +52 -54
- data/lib/grumlin/steps_serializers/bytecode.rb +53 -56
- data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +17 -21
- data/lib/grumlin/steps_serializers/serializer.rb +7 -11
- data/lib/grumlin/steps_serializers/string.rb +26 -30
- data/lib/grumlin/test/rspec/db_cleaner_context.rb +8 -12
- data/lib/grumlin/test/rspec/gremlin_context.rb +18 -16
- data/lib/grumlin/test/rspec.rb +1 -5
- data/lib/grumlin/transaction.rb +34 -36
- data/lib/grumlin/transport.rb +71 -73
- data/lib/grumlin/traversal_start.rb +31 -33
- data/lib/grumlin/traversal_strategies/options_strategy.rb +3 -7
- data/lib/grumlin/traverser.rb +5 -7
- data/lib/grumlin/typed_value.rb +11 -13
- data/lib/grumlin/typing.rb +70 -72
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin/vertex.rb +14 -16
- data/lib/grumlin/vertex_property.rb +14 -16
- data/lib/grumlin/with_extension.rb +17 -19
- data/lib/grumlin.rb +13 -0
- metadata +9 -6
- data/lib/grumlin/middlewares/frozen_builder.rb +0 -18
- data/lib/grumlin/sugar.rb +0 -15
@@ -1,43 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# constructor params: anonymous: true|false, default: false
|
8
|
-
# TODO: add pretty
|
3
|
+
class Grumlin::StepsSerializers::String < Grumlin::StepsSerializers::Serializer
|
4
|
+
# constructor params: apply_shortcuts: true|false, default: false
|
5
|
+
# constructor params: anonymous: true|false, default: false
|
6
|
+
# TODO: add pretty
|
9
7
|
|
10
|
-
|
11
|
-
|
8
|
+
def serialize
|
9
|
+
steps = @params[:apply_shortcuts] ? Grumlin::ShortcutsApplyer.call(@steps) : @steps
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
steps = [steps.configuration_steps, steps.steps].map do |stps|
|
12
|
+
stps.map { |step| serialize_step(step) }
|
13
|
+
end
|
16
14
|
|
17
|
-
|
18
|
-
|
15
|
+
"#{prefix}.#{(steps[0] + steps[1]).join(".")}"
|
16
|
+
end
|
19
17
|
|
20
|
-
|
18
|
+
private
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
def serialize_step(step)
|
21
|
+
"#{step.name}(#{(step.args + [step.params.any? ? step.params : nil].compact).map do |a|
|
22
|
+
serialize_arg(a)
|
23
|
+
end.join(", ")})"
|
24
|
+
end
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
def prefix
|
27
|
+
@prefix ||= @params[:anonymous] ? "__" : "g"
|
28
|
+
end
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
def serialize_arg(arg)
|
31
|
+
return "\"#{arg}\"" if arg.is_a?(::String) || arg.is_a?(Symbol)
|
32
|
+
return "#{arg.type}.#{arg.value}" if arg.is_a?(Grumlin::TypedValue)
|
33
|
+
return arg.to_s if arg.is_a?(Grumlin::Expressions::WithOptions)
|
36
34
|
|
37
|
-
|
35
|
+
return arg unless arg.is_a?(Grumlin::Steps)
|
38
36
|
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
37
|
+
Grumlin::StepsSerializers::String.new(arg, anonymous: true, **@params).serialize
|
42
38
|
end
|
43
39
|
end
|
@@ -1,19 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
module
|
5
|
-
|
6
|
-
module DBCleanerContext
|
7
|
-
end
|
3
|
+
module Grumlin::Test::RSpec
|
4
|
+
module DBCleanerContext
|
5
|
+
end
|
8
6
|
|
9
|
-
|
10
|
-
|
7
|
+
::RSpec.shared_context DBCleanerContext do
|
8
|
+
include DBCleanerContext
|
11
9
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
end
|
16
|
-
end
|
10
|
+
before do
|
11
|
+
g.E.drop.iterate
|
12
|
+
g.V.drop.iterate
|
17
13
|
end
|
18
14
|
end
|
19
15
|
end
|
@@ -1,25 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module Grumlin
|
4
|
-
module
|
5
|
-
|
6
|
-
module GremlinContext
|
7
|
-
end
|
3
|
+
module Grumlin::Test::RSpec
|
4
|
+
module GremlinContext
|
5
|
+
end
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
::RSpec.shared_context GremlinContext do
|
8
|
+
include GremlinContext
|
9
|
+
include Grumlin::Expressions
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
[:__, :g].each do |name|
|
12
|
+
define_method(name) do |cuts = Grumlin::Shortcuts::Storage.empty|
|
13
|
+
cuts.send(name)
|
14
|
+
end
|
15
|
+
end
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
before do
|
18
|
+
Grumlin::Expressions.constants.each do |tool|
|
19
|
+
stub_const(tool.to_s, Grumlin::Expressions.const_get(tool))
|
22
20
|
end
|
23
21
|
end
|
22
|
+
|
23
|
+
after do
|
24
|
+
Grumlin.close
|
25
|
+
end
|
24
26
|
end
|
25
27
|
end
|
data/lib/grumlin/test/rspec.rb
CHANGED
data/lib/grumlin/transaction.rb
CHANGED
@@ -1,40 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
pool: @pool)
|
38
|
-
end
|
3
|
+
class Grumlin::Transaction
|
4
|
+
attr_reader :session_id
|
5
|
+
|
6
|
+
include Console
|
7
|
+
|
8
|
+
COMMIT = Grumlin::Repository.new.g.step(:tx, :commit)
|
9
|
+
ROLLBACK = Grumlin::Repository.new.g.step(:tx, :rollback)
|
10
|
+
|
11
|
+
def initialize(traversal_start_class, pool:, middlewares:)
|
12
|
+
@traversal_start_class = traversal_start_class
|
13
|
+
@pool = pool
|
14
|
+
@session_id = SecureRandom.uuid
|
15
|
+
@middlewares = middlewares
|
16
|
+
end
|
17
|
+
|
18
|
+
def begin
|
19
|
+
@traversal_start_class.new(session_id: @session_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def commit
|
23
|
+
finalize(COMMIT)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rollback
|
27
|
+
finalize(ROLLBACK)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def finalize(step)
|
33
|
+
@middlewares.call(traversal: step,
|
34
|
+
need_results: false,
|
35
|
+
session_id: @session_id,
|
36
|
+
pool: @pool)
|
39
37
|
end
|
40
38
|
end
|
data/lib/grumlin/transport.rb
CHANGED
@@ -1,101 +1,99 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# and https://github.com/socketry/async-websocket
|
7
|
-
|
8
|
-
include Console
|
9
|
-
|
10
|
-
attr_reader :url
|
11
|
-
|
12
|
-
# Transport is not reusable. Once closed should be recreated.
|
13
|
-
def initialize(url, parent: Async::Task.current, **client_options)
|
14
|
-
@url = url
|
15
|
-
@parent = parent
|
16
|
-
@client_options = client_options
|
17
|
-
@request_channel = Async::Channel.new
|
18
|
-
@response_channel = Async::Channel.new
|
19
|
-
end
|
3
|
+
class Grumlin::Transport
|
4
|
+
# A transport based on https://github.com/socketry/async
|
5
|
+
# and https://github.com/socketry/async-websocket
|
20
6
|
|
21
|
-
|
22
|
-
!@connection.nil?
|
23
|
-
end
|
7
|
+
include Console
|
24
8
|
|
25
|
-
|
26
|
-
raise ClientClosedError if @closed
|
27
|
-
raise AlreadyConnectedError if connected?
|
9
|
+
attr_reader :url
|
28
10
|
|
29
|
-
|
30
|
-
|
11
|
+
# Transport is not reusable. Once closed should be recreated.
|
12
|
+
def initialize(url, parent: Async::Task.current, **client_options)
|
13
|
+
@url = url
|
14
|
+
@parent = parent
|
15
|
+
@client_options = client_options
|
16
|
+
@request_channel = Async::Channel.new
|
17
|
+
@response_channel = Async::Channel.new
|
18
|
+
end
|
31
19
|
|
32
|
-
|
20
|
+
def connected?
|
21
|
+
!@connection.nil?
|
22
|
+
end
|
33
23
|
|
34
|
-
|
24
|
+
def connect
|
25
|
+
raise ClientClosedError if @closed
|
26
|
+
raise AlreadyConnectedError if connected?
|
35
27
|
|
36
|
-
|
37
|
-
|
28
|
+
@connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
|
29
|
+
logger.debug(self) { "Connected to #{@url}." }
|
38
30
|
|
39
|
-
|
40
|
-
raise NotConnectedError unless connected?
|
31
|
+
@response_task = @parent.async { run_response_task }
|
41
32
|
|
42
|
-
|
43
|
-
end
|
33
|
+
@request_task = @parent.async { run_request_task }
|
44
34
|
|
45
|
-
|
46
|
-
|
35
|
+
@response_channel
|
36
|
+
end
|
47
37
|
|
48
|
-
|
38
|
+
def write(message)
|
39
|
+
raise NotConnectedError unless connected?
|
49
40
|
|
50
|
-
|
51
|
-
|
41
|
+
@request_channel << message
|
42
|
+
end
|
52
43
|
|
53
|
-
|
54
|
-
|
55
|
-
rescue StandardError
|
56
|
-
nil
|
57
|
-
end
|
58
|
-
@connection = nil
|
44
|
+
def close
|
45
|
+
return if @closed
|
59
46
|
|
60
|
-
|
61
|
-
|
62
|
-
|
47
|
+
@closed = true
|
48
|
+
|
49
|
+
@request_channel.close
|
50
|
+
@response_channel.close
|
63
51
|
|
64
|
-
|
65
|
-
@
|
66
|
-
|
52
|
+
begin
|
53
|
+
@connection.close
|
54
|
+
rescue StandardError
|
55
|
+
nil
|
67
56
|
end
|
57
|
+
@connection = nil
|
68
58
|
|
69
|
-
|
59
|
+
@request_task&.stop(true)
|
60
|
+
@response_task&.stop(true)
|
61
|
+
end
|
62
|
+
|
63
|
+
def wait
|
64
|
+
@request_task.wait
|
65
|
+
@response_task.wait
|
66
|
+
end
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
68
|
+
private
|
69
|
+
|
70
|
+
def run_response_task
|
71
|
+
with_guard do
|
72
|
+
loop do
|
73
|
+
data = @connection.read
|
74
|
+
@response_channel << data
|
77
75
|
end
|
78
76
|
end
|
77
|
+
end
|
79
78
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
end
|
79
|
+
def run_request_task
|
80
|
+
with_guard do
|
81
|
+
@request_channel.each do |message|
|
82
|
+
@connection.write(message)
|
83
|
+
@connection.flush
|
86
84
|
end
|
87
85
|
end
|
86
|
+
end
|
88
87
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
close
|
88
|
+
def with_guard
|
89
|
+
yield
|
90
|
+
rescue Async::Stop, Async::TimeoutError, StandardError => e
|
91
|
+
logger.debug(self) { "Guard error, closing." }
|
92
|
+
begin
|
93
|
+
@response_channel.exception(e)
|
94
|
+
rescue Async::Channel::ChannelClosedError
|
95
|
+
nil
|
99
96
|
end
|
97
|
+
close
|
100
98
|
end
|
101
99
|
end
|
@@ -1,42 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
begin
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
transaction.commit
|
25
|
-
end
|
3
|
+
class Grumlin::TraversalStart < Grumlin::Steppable
|
4
|
+
include Grumlin::WithExtension
|
5
|
+
|
6
|
+
class TraversalError < Grumlin::Error; end
|
7
|
+
class AlreadyBoundToTransactionError < TraversalError; end
|
8
|
+
|
9
|
+
def tx
|
10
|
+
raise AlreadyBoundToTransactionError if @session_id
|
11
|
+
|
12
|
+
transaction = tx_class.new(self.class, pool: @pool, middlewares: @middlewares)
|
13
|
+
return transaction unless block_given?
|
14
|
+
|
15
|
+
begin
|
16
|
+
yield transaction.begin
|
17
|
+
rescue Grumlin::Rollback
|
18
|
+
transaction.rollback
|
19
|
+
rescue StandardError
|
20
|
+
transaction.rollback
|
21
|
+
raise
|
22
|
+
else
|
23
|
+
transaction.commit
|
26
24
|
end
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
def to_s(*)
|
28
|
+
self.class.to_s
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def inspect
|
32
|
+
self.class.inspect
|
33
|
+
end
|
35
34
|
|
36
|
-
|
35
|
+
private
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
37
|
+
def tx_class
|
38
|
+
Grumlin.features.supports_transactions? ? Grumlin::Transaction : Grumlin::DummyTransaction
|
41
39
|
end
|
42
40
|
end
|
@@ -1,11 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(value)
|
7
|
-
super(type: "OptionsStrategy", value: value)
|
8
|
-
end
|
9
|
-
end
|
3
|
+
class Grumlin::TraversalStrategies::OptionsStrategy < Grumlin::TypedValue
|
4
|
+
def initialize(value)
|
5
|
+
super(type: "OptionsStrategy", value: value)
|
10
6
|
end
|
11
7
|
end
|
data/lib/grumlin/traverser.rb
CHANGED
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
attr_reader :bulk, :value
|
3
|
+
class Grumlin::Traverser
|
4
|
+
attr_reader :bulk, :value
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
6
|
+
def initialize(value)
|
7
|
+
@bulk = value.dig(:bulk, :@value) || 1
|
8
|
+
@value = Grumlin::Typing.cast(value[:value])
|
11
9
|
end
|
12
10
|
end
|
data/lib/grumlin/typed_value.rb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
attr_reader :type, :value
|
3
|
+
class Grumlin::TypedValue
|
4
|
+
attr_reader :type, :value
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
def initialize(type: nil, value: nil)
|
7
|
+
@type = type
|
8
|
+
@value = value
|
9
|
+
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def inspect
|
12
|
+
"<#{type}.#{value}>"
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
end
|
15
|
+
def to_s
|
16
|
+
inspect
|
19
17
|
end
|
20
18
|
end
|