grumlin 0.1.0 → 0.3.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 +67 -12
- data/.gitignore +1 -0
- data/.overcommit.yml +8 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +30 -23
- data/bin/console +6 -3
- data/bin/setup +1 -0
- data/docker-compose.yml +6 -0
- data/gremlin_server/Dockerfile +3 -0
- data/gremlin_server/tinkergraph-empty.properties +3 -0
- data/grumlin.gemspec +4 -3
- data/lib/grumlin.rb +46 -3
- data/lib/grumlin/anonymous_step.rb +44 -0
- data/lib/grumlin/client.rb +61 -131
- 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 +78 -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 +41 -13
- data/Rakefile +0 -12
- data/bin/stress +0 -51
- data/lib/grumlin/traversing_context.rb +0 -17
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
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
class Transport
|
5
|
+
# A transport based on https://github.com/socketry/async
|
6
|
+
# and https://github.com/socketry/async-websocket
|
7
|
+
def initialize(url, parent: Async::Task.current)
|
8
|
+
@endpoint = Async::HTTP::Endpoint.parse(url)
|
9
|
+
@parent = parent
|
10
|
+
@request_queue = Async::Queue.new
|
11
|
+
@response_queue = Async::Queue.new
|
12
|
+
reset!
|
13
|
+
end
|
14
|
+
|
15
|
+
def url
|
16
|
+
@endpoint.url
|
17
|
+
end
|
18
|
+
|
19
|
+
def connected?
|
20
|
+
@connected
|
21
|
+
end
|
22
|
+
|
23
|
+
def connect # rubocop:disable Metrics/MethodLength
|
24
|
+
raise AlreadyConnectedError if connected?
|
25
|
+
|
26
|
+
@connection = Async::WebSocket::Client.connect(@endpoint)
|
27
|
+
|
28
|
+
@response_task = @parent.async do
|
29
|
+
loop do
|
30
|
+
data = @connection.read
|
31
|
+
@response_queue << data
|
32
|
+
end
|
33
|
+
rescue Async::Stop
|
34
|
+
@response_queue << nil
|
35
|
+
end
|
36
|
+
|
37
|
+
@request_task = @parent.async do
|
38
|
+
@request_queue.each do |message|
|
39
|
+
@connection.write(message)
|
40
|
+
@connection.flush
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@connected = true
|
45
|
+
|
46
|
+
@response_queue
|
47
|
+
end
|
48
|
+
|
49
|
+
def write(message)
|
50
|
+
raise NotConnectedError unless connected?
|
51
|
+
|
52
|
+
@request_queue << message
|
53
|
+
end
|
54
|
+
|
55
|
+
def close
|
56
|
+
raise NotConnectedError unless connected?
|
57
|
+
|
58
|
+
@request_queue << nil
|
59
|
+
@request_task.wait
|
60
|
+
|
61
|
+
@response_task.stop
|
62
|
+
@response_task.wait
|
63
|
+
|
64
|
+
@connection.close
|
65
|
+
|
66
|
+
reset!
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def reset!
|
72
|
+
@connected = false
|
73
|
+
@connection = nil
|
74
|
+
@response_task = nil
|
75
|
+
@request_task = nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/grumlin/traversal.rb
CHANGED
@@ -4,21 +4,14 @@ module Grumlin
|
|
4
4
|
class Traversal
|
5
5
|
attr_reader :connection
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
Grumlin::Client.new(client_or_url)
|
10
|
-
else
|
11
|
-
client_or_url
|
12
|
-
end
|
13
|
-
|
14
|
-
return if block.nil?
|
15
|
-
|
16
|
-
TraversingContext.new(self).instance_exec(&block)
|
7
|
+
def initialize(pool = Grumlin.config.default_pool)
|
8
|
+
@pool = pool
|
17
9
|
end
|
18
10
|
|
11
|
+
# TODO: add other start steps
|
19
12
|
%w[addV addE V E].each do |step|
|
20
13
|
define_method step do |*args|
|
21
|
-
Step.new(@
|
14
|
+
Step.new(@pool, step, *args)
|
22
15
|
end
|
23
16
|
end
|
24
17
|
|
data/lib/grumlin/typing.rb
CHANGED
@@ -4,15 +4,19 @@ module Grumlin
|
|
4
4
|
module Typing
|
5
5
|
TYPES = {
|
6
6
|
"g:List" => ->(value) { value.map { |item| cast(item) } },
|
7
|
+
"g:Set" => ->(value) { Set.new(value.map { |item| cast(item) }) },
|
7
8
|
"g:Map" => ->(value) { cast_map(value) },
|
8
9
|
"g:Vertex" => ->(value) { cast_entity(Grumlin::Vertex, value) },
|
9
10
|
"g:Edge" => ->(value) { cast_entity(Grumlin::Edge, value) },
|
11
|
+
"g:Path" => ->(value) { cast_entity(Grumlin::Path, value) },
|
10
12
|
"g:Int64" => ->(value) { cast_int(value) },
|
11
13
|
"g:Int32" => ->(value) { cast_int(value) },
|
12
|
-
"g:
|
14
|
+
"g:Double" => ->(value) { cast_double(value) },
|
15
|
+
"g:Traverser" => ->(value) { cast(value[:value]) }, # TODO: wtf is bulk?
|
16
|
+
"g:T" => ->(value) { value.to_sym }
|
13
17
|
}.freeze
|
14
18
|
|
15
|
-
CASTABLE_TYPES = [Hash, String, Integer].freeze
|
19
|
+
CASTABLE_TYPES = [Hash, String, Integer, TrueClass, FalseClass].freeze
|
16
20
|
|
17
21
|
class << self
|
18
22
|
def cast(value)
|
@@ -27,6 +31,13 @@ module Grumlin
|
|
27
31
|
type.call(value[:@value])
|
28
32
|
end
|
29
33
|
|
34
|
+
def to_bytecode(step)
|
35
|
+
{
|
36
|
+
"@type": "g:Bytecode",
|
37
|
+
"@value": { step: step }
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
30
41
|
private
|
31
42
|
|
32
43
|
def castable_type?(value); end
|
@@ -47,6 +58,12 @@ module Grumlin
|
|
47
58
|
value
|
48
59
|
end
|
49
60
|
|
61
|
+
def cast_double(value)
|
62
|
+
raise TypeError, "#{value} is not a Double" unless value.is_a?(Float)
|
63
|
+
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
50
67
|
def cast_entity(entity, value)
|
51
68
|
entity.new(**value)
|
52
69
|
rescue ArgumentError, TypeError
|
@@ -54,12 +71,15 @@ module Grumlin
|
|
54
71
|
end
|
55
72
|
|
56
73
|
def cast_map(value)
|
57
|
-
Hash[*value].transform_keys
|
74
|
+
Hash[*value].transform_keys do |key|
|
75
|
+
next key.to_sym if key.respond_to?(:to_sym)
|
76
|
+
next cast(key) if key[:@type]
|
77
|
+
|
78
|
+
raise UnknownMapKey, key, value
|
79
|
+
end.transform_values { |v| cast(v) }
|
58
80
|
rescue ArgumentError
|
59
81
|
raise TypeError, "#{value} cannot be casted to Hash"
|
60
82
|
end
|
61
|
-
|
62
|
-
def cast_traverser(value); end
|
63
83
|
end
|
64
84
|
end
|
65
85
|
end
|
data/lib/grumlin/u.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Grumlin
|
4
|
+
module U
|
5
|
+
module U
|
6
|
+
extend self # rubocop:disable Style/ModuleFunction
|
7
|
+
|
8
|
+
%w[addV V has count out values unfold].each do |step|
|
9
|
+
define_method step do |*args|
|
10
|
+
AnonymousStep.new(step, *args)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# TODO: add alias __
|
16
|
+
extend U
|
17
|
+
end
|
18
|
+
end
|
data/lib/grumlin/version.rb
CHANGED