grumlin 0.1.1 → 0.4.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 +4 -4
- data/.github/workflows/main.yml +1 -1
- data/.overcommit.yml +8 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +31 -22
- data/bin/console +6 -3
- data/bin/setup +1 -0
- data/grumlin.gemspec +4 -3
- data/lib/async/channel.rb +64 -0
- data/lib/grumlin.rb +49 -3
- data/lib/grumlin/anonymous_step.rb +44 -0
- data/lib/grumlin/client.rb +68 -132
- data/lib/grumlin/edge.rb +2 -2
- data/lib/grumlin/exceptions.rb +22 -2
- data/lib/grumlin/order.rb +22 -0
- data/lib/grumlin/p.rb +18 -0
- data/lib/grumlin/path.rb +15 -0
- data/lib/grumlin/pop.rb +32 -0
- data/lib/grumlin/request_dispatcher.rb +84 -0
- data/lib/grumlin/step.rb +18 -29
- data/lib/grumlin/sugar.rb +42 -0
- data/lib/grumlin/t.rb +22 -0
- data/lib/grumlin/test/rspec.rb +11 -0
- data/lib/grumlin/test/rspec/db_cleaner_context.rb +18 -0
- data/lib/grumlin/test/rspec/gremlin_context.rb +21 -0
- data/lib/grumlin/translator.rb +22 -21
- data/lib/grumlin/transport.rb +86 -0
- data/lib/grumlin/traversal.rb +4 -11
- data/lib/grumlin/typing.rb +25 -5
- data/lib/grumlin/u.rb +18 -0
- data/lib/grumlin/version.rb +1 -1
- data/lib/grumlin/vertex.rb +2 -2
- metadata +36 -9
- data/bin/stress +0 -51
- data/lib/grumlin/traversing_context.rb +0 -17
data/lib/grumlin/edge.rb
CHANGED
@@ -15,11 +15,11 @@ module Grumlin
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def ==(other)
|
18
|
-
@label == other.label && @id == other.id
|
18
|
+
self.class == other.class && @label == other.label && @id == other.id
|
19
19
|
end
|
20
20
|
|
21
21
|
def inspect
|
22
|
-
"
|
22
|
+
"e[#{@id}][#{@inV}-#{@label}->#{@outV}]"
|
23
23
|
end
|
24
24
|
alias to_s inspect
|
25
25
|
end
|
data/lib/grumlin/exceptions.rb
CHANGED
@@ -3,8 +3,14 @@
|
|
3
3
|
module Grumlin
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
|
+
class UnknownError < Error; end
|
7
|
+
|
6
8
|
class ConnectionError < Error; end
|
7
9
|
|
10
|
+
class CannotConnectError < ConnectionError; end
|
11
|
+
|
12
|
+
class DisconnectError < ConnectionError; end
|
13
|
+
|
8
14
|
class ConnectionStatusError < Error; end
|
9
15
|
|
10
16
|
class NotConnectedError < ConnectionStatusError; end
|
@@ -24,8 +30,6 @@ module Grumlin
|
|
24
30
|
|
25
31
|
class UnknownTypeError < ProtocolError; end
|
26
32
|
|
27
|
-
class ConnectionClosedError < Error; end
|
28
|
-
|
29
33
|
class StatusError < Error
|
30
34
|
attr_reader :status
|
31
35
|
|
@@ -48,4 +52,20 @@ module Grumlin
|
|
48
52
|
class ServerSerializationError < ServerSideError; end
|
49
53
|
|
50
54
|
class ServerTimeoutError < ServerSideError; end
|
55
|
+
|
56
|
+
class InternalClientError < Error; end
|
57
|
+
|
58
|
+
class UnknownRequestStoppedError < InternalClientError; end
|
59
|
+
|
60
|
+
class ResourceLeakError < InternalClientError; end
|
61
|
+
|
62
|
+
class UnknownMapKey < InternalClientError
|
63
|
+
attr_reader :key, :map
|
64
|
+
|
65
|
+
def initialize(key, map)
|
66
|
+
@key = key
|
67
|
+
@map = map
|
68
|
+
super("Cannot cast key #{key} in map #{map}")
|
69
|
+
end
|
70
|
+
end
|
51
71
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Order
|
5
|
+
module Order
|
6
|
+
DESC = { "@type": "g:Order", "@value": "desc" }.freeze
|
7
|
+
ASC = { "@type": "g:Order", "@value": "desc" }.freeze
|
8
|
+
|
9
|
+
extend self # rubocop:disable Style/ModuleFunction
|
10
|
+
|
11
|
+
def asc
|
12
|
+
ASC
|
13
|
+
end
|
14
|
+
|
15
|
+
def desc
|
16
|
+
DESC
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
extend Order
|
21
|
+
end
|
22
|
+
end
|
data/lib/grumlin/p.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module P
|
5
|
+
module P
|
6
|
+
%w[within].each do |step|
|
7
|
+
define_method step do |*args|
|
8
|
+
{ # TODO: replace with a class?
|
9
|
+
"@type": "g:P",
|
10
|
+
"@value": { predicate: "within", value: { "@type": "g:List", "@value": args } }
|
11
|
+
}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
extend P
|
17
|
+
end
|
18
|
+
end
|
data/lib/grumlin/path.rb
ADDED
data/lib/grumlin/pop.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Pop
|
5
|
+
module Pop
|
6
|
+
extend self # rubocop:disable Style/ModuleFunction
|
7
|
+
|
8
|
+
FIRST = { "@type": "g:Pop", "@value": "first" }.freeze
|
9
|
+
LAST = { "@type": "g:Pop", "@value": "last" }.freeze
|
10
|
+
ALL = { "@type": "g:Pop", "@value": "all" }.freeze
|
11
|
+
MIXED = { "@type": "g:Pop", "@value": "mixed" }.freeze
|
12
|
+
|
13
|
+
def first
|
14
|
+
FIRST
|
15
|
+
end
|
16
|
+
|
17
|
+
def last
|
18
|
+
LAST
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
ALL
|
23
|
+
end
|
24
|
+
|
25
|
+
def mixed
|
26
|
+
MIXED
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
extend Pop
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
class RequestDispatcher
|
5
|
+
attr_reader :requests
|
6
|
+
|
7
|
+
SUCCESS = {
|
8
|
+
200 => :success,
|
9
|
+
204 => :no_content,
|
10
|
+
206 => :partial_content
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
ERRORS = {
|
14
|
+
499 => InvalidRequestArgumentsError,
|
15
|
+
500 => ServerError,
|
16
|
+
597 => ScriptEvaluationError,
|
17
|
+
599 => ServerSerializationError,
|
18
|
+
598 => ServerTimeoutError,
|
19
|
+
|
20
|
+
401 => ClientSideError,
|
21
|
+
407 => ClientSideError,
|
22
|
+
498 => ClientSideError
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@requests = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_request(request)
|
30
|
+
raise "ERROR" if @requests.key?(request[:requestId])
|
31
|
+
|
32
|
+
Async::Channel.new.tap do |channel|
|
33
|
+
@requests[request[:requestId]] = { request: request, result: [], channel: channel }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# builds a response object, when it's ready sends it to the client via a channel
|
38
|
+
# TODO: sometimes response does not include requestID, no idea how to handle it so far.
|
39
|
+
def add_response(response) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
40
|
+
request_id = response[:requestId]
|
41
|
+
raise "ERROR" unless ongoing_request?(request_id)
|
42
|
+
|
43
|
+
request = @requests[request_id]
|
44
|
+
|
45
|
+
check_errors!(response[:status])
|
46
|
+
|
47
|
+
case SUCCESS[response.dig(:status, :code)]
|
48
|
+
when :success
|
49
|
+
request[:channel] << request[:result] + [response.dig(:result, :data)]
|
50
|
+
close_request(request_id)
|
51
|
+
when :partial_content then request[:result] << response.dig(:result, :data)
|
52
|
+
when :no_content
|
53
|
+
request[:channel] << []
|
54
|
+
close_request(request_id)
|
55
|
+
end
|
56
|
+
rescue StandardError => e
|
57
|
+
request[:channel].exception(e)
|
58
|
+
close_request(request_id)
|
59
|
+
end
|
60
|
+
|
61
|
+
def close_request(request_id)
|
62
|
+
raise "ERROR" unless ongoing_request?(request_id)
|
63
|
+
|
64
|
+
request = @requests.delete(request_id)
|
65
|
+
request[:channel].close
|
66
|
+
end
|
67
|
+
|
68
|
+
def ongoing_request?(request_id)
|
69
|
+
@requests.key?(request_id)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def check_errors!(status)
|
75
|
+
if (error = ERRORS[status[:code]])
|
76
|
+
raise(error, status)
|
77
|
+
end
|
78
|
+
|
79
|
+
return unless SUCCESS[status[:code]].nil?
|
80
|
+
|
81
|
+
raise(UnknownResponseStatus, status)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/grumlin/step.rb
CHANGED
@@ -1,46 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Grumlin
|
4
|
-
class Step
|
5
|
-
attr_reader :client
|
4
|
+
class Step < AnonymousStep
|
5
|
+
attr_reader :client
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
@
|
10
|
-
@name = name
|
11
|
-
@previous_steps = previous_steps
|
12
|
-
@args = args
|
7
|
+
def initialize(pool, name, *args, previous_steps: [])
|
8
|
+
super(name, *args, previous_steps: previous_steps)
|
9
|
+
@pool = pool
|
13
10
|
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
12
|
+
def next
|
13
|
+
@enum ||= toList.to_enum
|
14
|
+
@enum.next
|
19
15
|
end
|
20
16
|
|
21
|
-
alias addVertex addV
|
22
|
-
alias addEdge addE
|
23
|
-
|
24
|
-
# TODO: add support for next
|
25
|
-
# TODO: add support for iterate
|
26
|
-
# TODO: memoization
|
27
17
|
def toList # rubocop:disable Naming/MethodName
|
28
|
-
@client
|
29
|
-
|
30
|
-
|
31
|
-
def inspect
|
32
|
-
"<Step #{self}>" # TODO: substitute bindings
|
18
|
+
@pool.acquire do |client|
|
19
|
+
client.write(*steps)
|
20
|
+
end
|
33
21
|
end
|
34
22
|
|
35
|
-
|
36
|
-
|
37
|
-
|
23
|
+
def iterate
|
24
|
+
@pool.acquire do |client|
|
25
|
+
client.write(*(steps + [nil]))
|
26
|
+
end
|
38
27
|
end
|
39
28
|
|
40
|
-
|
29
|
+
private
|
41
30
|
|
42
|
-
def
|
43
|
-
(@previous_steps
|
31
|
+
def add_step(step_name, args, previous_steps:)
|
32
|
+
self.class.new(@pool, step_name, *args, previous_steps: previous_steps)
|
44
33
|
end
|
45
34
|
end
|
46
35
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Sugar
|
5
|
+
# TODO: how to use it in specs?
|
6
|
+
HELPERS = [
|
7
|
+
Grumlin::U,
|
8
|
+
Grumlin::T,
|
9
|
+
Grumlin::P,
|
10
|
+
Grumlin::Pop,
|
11
|
+
Grumlin::Order
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.extend ClassMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def const_missing(name)
|
20
|
+
helper = HELPERS.find { |h| h.const_defined?(name) }
|
21
|
+
super if helper.nil?
|
22
|
+
|
23
|
+
const_set(name, helper)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def const_missing(name)
|
28
|
+
helper = HELPERS.find { |h| h.const_defined?(name) }
|
29
|
+
super if helper.nil?
|
30
|
+
|
31
|
+
const_set(name, helper)
|
32
|
+
end
|
33
|
+
|
34
|
+
def __
|
35
|
+
Grumlin::U
|
36
|
+
end
|
37
|
+
|
38
|
+
def g
|
39
|
+
Grumlin::Traversal.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/grumlin/t.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module T
|
5
|
+
module T
|
6
|
+
T_ID = { :@type => "g:T", :@value => "id" }.freeze # TODO: replace with a class?
|
7
|
+
T_LABEL = { :@type => "g:T", :@value => "label" }.freeze # TODO: replace with a class?
|
8
|
+
|
9
|
+
extend self # rubocop:disable Style/ModuleFunction
|
10
|
+
|
11
|
+
def id
|
12
|
+
T_ID
|
13
|
+
end
|
14
|
+
|
15
|
+
def label
|
16
|
+
T_LABEL
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
extend T
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Test
|
5
|
+
module RSpec
|
6
|
+
module DBCleanerContext
|
7
|
+
end
|
8
|
+
|
9
|
+
::RSpec.shared_context DBCleanerContext do
|
10
|
+
include DBCleanerContext
|
11
|
+
|
12
|
+
before do
|
13
|
+
g.V().drop.iterate
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module Test
|
5
|
+
module RSpec
|
6
|
+
module GremlinContext
|
7
|
+
end
|
8
|
+
|
9
|
+
::RSpec.shared_context GremlinContext do
|
10
|
+
include GremlinContext
|
11
|
+
|
12
|
+
let(:g) { Grumlin::Traversal.new }
|
13
|
+
|
14
|
+
after do
|
15
|
+
Grumlin.config.default_pool.close
|
16
|
+
Grumlin.config.reset!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/grumlin/translator.rb
CHANGED
@@ -3,38 +3,39 @@
|
|
3
3
|
module Grumlin
|
4
4
|
module Translator
|
5
5
|
class << self
|
6
|
-
def
|
7
|
-
|
8
|
-
string_steps, bindings = steps.each_with_object([[], {}]) do |step, (acc_g, acc_b)|
|
9
|
-
args = step.args.map do |arg|
|
10
|
-
binding_name(counter).tap do |b|
|
11
|
-
acc_b[b] = arg
|
12
|
-
counter += 1
|
13
|
-
end
|
14
|
-
end.join(", ")
|
15
|
-
|
16
|
-
acc_g << "#{step.name}(#{args})"
|
17
|
-
end
|
6
|
+
def to_bytecode(steps)
|
7
|
+
return arg_to_bytecode(steps) if steps.is_a?(AnonymousStep)
|
18
8
|
|
19
|
-
|
9
|
+
steps.map do |step|
|
10
|
+
arg_to_bytecode(step)
|
11
|
+
end
|
20
12
|
end
|
21
13
|
|
22
14
|
def to_bytecode_query(steps)
|
23
15
|
steps.map do |step|
|
24
|
-
|
16
|
+
arg_to_query_bytecode(step)
|
25
17
|
end
|
26
18
|
end
|
27
19
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
20
|
+
private
|
21
|
+
|
22
|
+
def arg_to_bytecode(arg)
|
23
|
+
return arg unless arg.is_a?(AnonymousStep)
|
24
|
+
|
25
|
+
args = arg.args.flatten.map do |a|
|
26
|
+
a.instance_of?(AnonymousStep) ? to_bytecode(a.steps) : arg_to_bytecode(a)
|
27
|
+
end
|
28
|
+
[arg.name, *args]
|
32
29
|
end
|
33
30
|
|
34
|
-
|
31
|
+
def arg_to_query_bytecode(arg)
|
32
|
+
return ["none"] if arg.nil?
|
33
|
+
return arg unless arg.is_a?(AnonymousStep)
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
args = arg.args.flatten.map do |a|
|
36
|
+
a.instance_of?(AnonymousStep) ? Typing.to_bytecode(to_bytecode(a.steps)) : arg_to_query_bytecode(a)
|
37
|
+
end
|
38
|
+
[arg.name, *args]
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|