grumlin 0.1.3 → 0.2.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/.overcommit.yml +8 -0
- data/.rubocop.yml +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +29 -21
- data/bin/console +6 -3
- data/bin/setup +1 -0
- data/grumlin.gemspec +1 -1
- data/lib/grumlin.rb +34 -1
- data/lib/grumlin/anonymous_step.rb +44 -0
- data/lib/grumlin/client.rb +55 -106
- 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/step.rb +12 -27
- 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 +22 -0
- data/lib/grumlin/translator.rb +22 -21
- data/lib/grumlin/transport/async.rb +95 -0
- data/lib/grumlin/traversal.rb +2 -5
- 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 +18 -7
- data/bin/stress +0 -51
- data/lib/grumlin/traversing_context.rb +0 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c0290e4d77bd50f701a803390ff2a0489b039f4beb231e8883ced690180c7abc
|
|
4
|
+
data.tar.gz: b4948d687c09ffea5bf9da26eda8644fc3fcffa1954e4e61fe2d628192984e7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3846fe491a606989f51f0f609e8c7ae2049b8a1005272ea531f085402e7d998a7f8723ccdafae01ce7d913f9ad9272f9da1e86a307cb7aeb5066ef4ad4024e4b
|
|
7
|
+
data.tar.gz: 1b20d251d16c95cb12657c6becbbb4592b8e206e9e83a7defee42281bbdd7c3f84d04785b2cc89bcc1328d05d11aa14b865b6bc6832899d3a73ce7b86283be5b
|
data/.overcommit.yml
ADDED
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
|
@@ -4,6 +4,7 @@ source "https://rubygems.org"
|
|
|
4
4
|
|
|
5
5
|
gemspec
|
|
6
6
|
|
|
7
|
+
gem "nokogiri"
|
|
7
8
|
gem "rubocop"
|
|
8
9
|
gem "rubocop-performance"
|
|
9
10
|
gem "rubocop-rspec"
|
|
@@ -12,5 +13,6 @@ gem "solargraph"
|
|
|
12
13
|
|
|
13
14
|
gem "async-rspec"
|
|
14
15
|
gem "factory_bot"
|
|
16
|
+
gem "overcommit"
|
|
15
17
|
gem "rspec"
|
|
16
18
|
gem "simplecov"
|
data/Gemfile.lock
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
grumlin (0.
|
|
5
|
-
async-websocket (~> 0.
|
|
4
|
+
grumlin (0.2.0)
|
|
5
|
+
async-websocket (~> 0.19)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
@@ -14,33 +14,34 @@ GEM
|
|
|
14
14
|
tzinfo (~> 2.0)
|
|
15
15
|
zeitwerk (~> 2.3)
|
|
16
16
|
ast (2.4.2)
|
|
17
|
-
async (1.
|
|
17
|
+
async (1.30.0)
|
|
18
18
|
console (~> 1.10)
|
|
19
19
|
nio4r (~> 2.3)
|
|
20
20
|
timers (~> 4.1)
|
|
21
|
-
async-http (0.56.
|
|
22
|
-
async (
|
|
23
|
-
async-io (
|
|
24
|
-
async-pool (
|
|
21
|
+
async-http (0.56.5)
|
|
22
|
+
async (>= 1.25)
|
|
23
|
+
async-io (>= 1.28)
|
|
24
|
+
async-pool (>= 0.2)
|
|
25
25
|
protocol-http (~> 0.22.0)
|
|
26
26
|
protocol-http1 (~> 0.14.0)
|
|
27
27
|
protocol-http2 (~> 0.14.0)
|
|
28
|
-
async-io (1.
|
|
29
|
-
async
|
|
30
|
-
async-pool (0.3.
|
|
31
|
-
async (
|
|
28
|
+
async-io (1.32.2)
|
|
29
|
+
async
|
|
30
|
+
async-pool (0.3.8)
|
|
31
|
+
async (>= 1.25)
|
|
32
32
|
async-rspec (1.16.0)
|
|
33
33
|
rspec (~> 3.0)
|
|
34
34
|
rspec-files (~> 1.0)
|
|
35
35
|
rspec-memory (~> 1.0)
|
|
36
|
-
async-websocket (0.
|
|
36
|
+
async-websocket (0.19.0)
|
|
37
37
|
async-http (~> 0.54)
|
|
38
38
|
async-io (~> 1.23)
|
|
39
39
|
protocol-websocket (~> 0.7.0)
|
|
40
|
-
backport (1.
|
|
40
|
+
backport (1.2.0)
|
|
41
41
|
benchmark (0.1.1)
|
|
42
|
+
childprocess (4.0.0)
|
|
42
43
|
concurrent-ruby (1.1.8)
|
|
43
|
-
console (1.
|
|
44
|
+
console (1.13.1)
|
|
44
45
|
fiber-local
|
|
45
46
|
diff-lcs (1.4.4)
|
|
46
47
|
docile (1.4.0)
|
|
@@ -50,6 +51,7 @@ GEM
|
|
|
50
51
|
fiber-local (1.0.0)
|
|
51
52
|
i18n (1.8.10)
|
|
52
53
|
concurrent-ruby (~> 1.0)
|
|
54
|
+
iniparse (1.5.0)
|
|
53
55
|
jaro_winkler (1.5.4)
|
|
54
56
|
kramdown (2.3.1)
|
|
55
57
|
rexml
|
|
@@ -57,13 +59,16 @@ GEM
|
|
|
57
59
|
kramdown (~> 2.0)
|
|
58
60
|
minitest (5.14.4)
|
|
59
61
|
nio4r (2.5.7)
|
|
60
|
-
nokogiri (1.11.
|
|
62
|
+
nokogiri (1.11.7-x86_64-linux)
|
|
61
63
|
racc (~> 1.4)
|
|
64
|
+
overcommit (0.57.0)
|
|
65
|
+
childprocess (>= 0.6.3, < 5)
|
|
66
|
+
iniparse (~> 1.4)
|
|
62
67
|
parallel (1.20.1)
|
|
63
68
|
parser (3.0.1.1)
|
|
64
69
|
ast (~> 2.4.1)
|
|
65
70
|
protocol-hpack (1.4.2)
|
|
66
|
-
protocol-http (0.22.
|
|
71
|
+
protocol-http (0.22.5)
|
|
67
72
|
protocol-http1 (0.14.1)
|
|
68
73
|
protocol-http (~> 0.22)
|
|
69
74
|
protocol-http2 (0.14.2)
|
|
@@ -95,16 +100,16 @@ GEM
|
|
|
95
100
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
96
101
|
rspec-support (~> 3.10.0)
|
|
97
102
|
rspec-support (3.10.2)
|
|
98
|
-
rubocop (1.
|
|
103
|
+
rubocop (1.16.1)
|
|
99
104
|
parallel (~> 1.10)
|
|
100
105
|
parser (>= 3.0.0.0)
|
|
101
106
|
rainbow (>= 2.2.2, < 4.0)
|
|
102
107
|
regexp_parser (>= 1.8, < 3.0)
|
|
103
108
|
rexml
|
|
104
|
-
rubocop-ast (>= 1.
|
|
109
|
+
rubocop-ast (>= 1.7.0, < 2.0)
|
|
105
110
|
ruby-progressbar (~> 1.7)
|
|
106
111
|
unicode-display_width (>= 1.4.0, < 3.0)
|
|
107
|
-
rubocop-ast (1.
|
|
112
|
+
rubocop-ast (1.7.0)
|
|
108
113
|
parser (>= 3.0.1.1)
|
|
109
114
|
rubocop-performance (1.11.3)
|
|
110
115
|
rubocop (>= 1.7.0, < 2.0)
|
|
@@ -119,10 +124,11 @@ GEM
|
|
|
119
124
|
simplecov_json_formatter (~> 0.1)
|
|
120
125
|
simplecov-html (0.12.3)
|
|
121
126
|
simplecov_json_formatter (0.1.3)
|
|
122
|
-
solargraph (0.
|
|
123
|
-
backport (~> 1.
|
|
127
|
+
solargraph (0.42.4)
|
|
128
|
+
backport (~> 1.2)
|
|
124
129
|
benchmark
|
|
125
130
|
bundler (>= 1.17.2)
|
|
131
|
+
diff-lcs (~> 1.4)
|
|
126
132
|
e2mmap
|
|
127
133
|
jaro_winkler (~> 1.5)
|
|
128
134
|
kramdown (~> 2.3)
|
|
@@ -149,6 +155,8 @@ DEPENDENCIES
|
|
|
149
155
|
async-rspec
|
|
150
156
|
factory_bot
|
|
151
157
|
grumlin!
|
|
158
|
+
nokogiri
|
|
159
|
+
overcommit
|
|
152
160
|
rspec
|
|
153
161
|
rubocop
|
|
154
162
|
rubocop-performance
|
data/bin/console
CHANGED
|
@@ -5,9 +5,12 @@ require "bundler/setup"
|
|
|
5
5
|
require "grumlin"
|
|
6
6
|
require "irb"
|
|
7
7
|
|
|
8
|
+
Grumlin.configure do |config|
|
|
9
|
+
config.url = ENV["GREMLIN_URL"] || "ws://localhost:8182/gremlin"
|
|
10
|
+
end
|
|
11
|
+
|
|
8
12
|
Async do
|
|
9
|
-
|
|
10
|
-
g = Grumlin::Traversal.new(client)
|
|
13
|
+
g = Grumlin::Traversal.new
|
|
11
14
|
|
|
12
15
|
IRB.setup(nil)
|
|
13
16
|
workspace = IRB::WorkSpace.new(binding)
|
|
@@ -16,5 +19,5 @@ Async do
|
|
|
16
19
|
rescue StandardError
|
|
17
20
|
raise
|
|
18
21
|
ensure
|
|
19
|
-
|
|
22
|
+
Grumlin.config.default_client&.disconnect
|
|
20
23
|
end
|
data/bin/setup
CHANGED
data/grumlin.gemspec
CHANGED
data/lib/grumlin.rb
CHANGED
|
@@ -5,20 +5,53 @@ require "json"
|
|
|
5
5
|
|
|
6
6
|
require "async"
|
|
7
7
|
require "async/queue"
|
|
8
|
+
require "async/barrier"
|
|
8
9
|
require "async/http/endpoint"
|
|
9
10
|
require "async/websocket/client"
|
|
10
11
|
|
|
11
12
|
require_relative "grumlin/version"
|
|
12
13
|
require_relative "grumlin/exceptions"
|
|
13
14
|
|
|
15
|
+
require_relative "grumlin/transport/async"
|
|
16
|
+
|
|
14
17
|
require_relative "grumlin/vertex"
|
|
15
18
|
require_relative "grumlin/edge"
|
|
19
|
+
require_relative "grumlin/path"
|
|
16
20
|
require_relative "grumlin/typing"
|
|
17
21
|
require_relative "grumlin/client"
|
|
18
22
|
require_relative "grumlin/traversal"
|
|
23
|
+
|
|
24
|
+
require_relative "grumlin/anonymous_step"
|
|
19
25
|
require_relative "grumlin/step"
|
|
26
|
+
|
|
20
27
|
require_relative "grumlin/translator"
|
|
21
|
-
require_relative "grumlin/
|
|
28
|
+
require_relative "grumlin/t"
|
|
29
|
+
require_relative "grumlin/order"
|
|
30
|
+
require_relative "grumlin/u"
|
|
31
|
+
require_relative "grumlin/p"
|
|
32
|
+
require_relative "grumlin/pop"
|
|
33
|
+
require_relative "grumlin/sugar"
|
|
22
34
|
|
|
23
35
|
module Grumlin
|
|
36
|
+
class Config
|
|
37
|
+
attr_accessor :url
|
|
38
|
+
|
|
39
|
+
def default_client
|
|
40
|
+
@default_client ||= Grumlin::Client.new(url)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def reset!
|
|
44
|
+
@default_client = nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
def configure
|
|
50
|
+
yield config
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def config
|
|
54
|
+
@config ||= Config.new
|
|
55
|
+
end
|
|
56
|
+
end
|
|
24
57
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grumlin
|
|
4
|
+
class AnonymousStep
|
|
5
|
+
attr_reader :name, :args
|
|
6
|
+
|
|
7
|
+
def initialize(name, *args, previous_steps: [])
|
|
8
|
+
@name = name
|
|
9
|
+
@previous_steps = previous_steps
|
|
10
|
+
@args = args
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
%w[addV addE V E limit count drop property valueMap select from to as order by has hasLabel values hasNot
|
|
14
|
+
not outE groupCount label group in out fold unfold inV path dedup project coalesce repeat emit
|
|
15
|
+
elementMap where].each do |step|
|
|
16
|
+
define_method step do |*args|
|
|
17
|
+
add_step(step, args, previous_steps: steps)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
alias addVertex addV
|
|
22
|
+
alias addEdge addE
|
|
23
|
+
|
|
24
|
+
def inspect
|
|
25
|
+
@inspect ||= to_bytecode.to_s
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
alias to_s inspect
|
|
29
|
+
|
|
30
|
+
def to_bytecode
|
|
31
|
+
@to_bytecode ||= (@previous_steps.last&.to_bytecode || []) + [Translator.to_bytecode(self)]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def steps
|
|
35
|
+
(@previous_steps + [self])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def add_step(step_name, args, previous_steps:)
|
|
41
|
+
self.class.new(step_name, *args, previous_steps: previous_steps)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/grumlin/client.rb
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Grumlin
|
|
4
|
-
class Client
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
class Client
|
|
5
|
+
extend Forwardable
|
|
6
|
+
|
|
7
|
+
SUCCESS = {
|
|
8
|
+
200 => :success,
|
|
9
|
+
204 => :no_content,
|
|
10
|
+
206 => :partial_content
|
|
11
|
+
}.freeze
|
|
8
12
|
|
|
9
13
|
ERRORS = {
|
|
10
14
|
499 => InvalidRequestArgumentsError,
|
|
@@ -18,126 +22,80 @@ module Grumlin
|
|
|
18
22
|
498 => ClientSideError
|
|
19
23
|
}.freeze
|
|
20
24
|
|
|
21
|
-
def initialize(url,
|
|
22
|
-
@
|
|
23
|
-
@
|
|
24
|
-
@mode = mode
|
|
25
|
-
|
|
26
|
-
@requests = {}
|
|
27
|
-
@query_queue = Async::Queue.new
|
|
28
|
-
|
|
25
|
+
def initialize(url, autoconnect: true)
|
|
26
|
+
@url = url
|
|
27
|
+
@transport = Transport::Async.new(url)
|
|
29
28
|
connect if autoconnect
|
|
30
29
|
end
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
raise AlreadyConnectedError unless @connection_task.nil?
|
|
31
|
+
def_delegators :@transport, :connect, :disconnect, :requests
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
queue << [:error, e]
|
|
43
|
-
end
|
|
44
|
-
disconnect
|
|
45
|
-
end
|
|
33
|
+
# TODO: support yielding
|
|
34
|
+
def submit(*args)
|
|
35
|
+
request_id = SecureRandom.uuid
|
|
36
|
+
queue = @transport.submit(to_query(request_id, args))
|
|
37
|
+
wait_for_response(request_id, queue)
|
|
38
|
+
ensure
|
|
39
|
+
@transport.close_request(request_id)
|
|
46
40
|
end
|
|
47
41
|
|
|
48
|
-
def
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@connection_task&.stop
|
|
52
|
-
@connection_task&.wait
|
|
53
|
-
@connection_task = nil
|
|
54
|
-
@requests = {}
|
|
42
|
+
def inspect
|
|
43
|
+
"<#{self.class} @url=#{@url}>"
|
|
55
44
|
end
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
response_queue, request_id = schedule_query(args)
|
|
59
|
-
result = []
|
|
46
|
+
alias to_s inspect
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
reraise_error!(response) if status == :error
|
|
48
|
+
private
|
|
63
49
|
|
|
64
|
-
|
|
50
|
+
def wait_for_response(request_id, queue, result: []) # rubocop:disable Metrics/MethodLength
|
|
51
|
+
queue.each do |status, response|
|
|
52
|
+
check_errors!(request_id, status, response)
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
case SUCCESS[response.dig(:status, :code)]
|
|
55
|
+
when :success
|
|
56
|
+
return result + Typing.cast(response.dig(:result, :data))
|
|
57
|
+
when :partial_content then result += Typing.cast(response.dig(:result, :data))
|
|
58
|
+
when :no_content
|
|
68
59
|
return []
|
|
69
60
|
end
|
|
70
|
-
|
|
71
|
-
check_errors!(status, request_id)
|
|
72
|
-
|
|
73
|
-
page = Typing.cast(response.dig(:result, :data))
|
|
74
|
-
|
|
75
|
-
case status[:code]
|
|
76
|
-
when SUCCESS_STATUS
|
|
77
|
-
close_request(request_id)
|
|
78
|
-
return result + page
|
|
79
|
-
when PARTIAL_CONTENT_STATUS
|
|
80
|
-
result += page
|
|
81
|
-
else
|
|
82
|
-
raise UnknownResponseStatus, status
|
|
83
|
-
end
|
|
84
61
|
end
|
|
62
|
+
rescue ::Async::Stop
|
|
63
|
+
retry if @transport.ongoing_request?(request_id)
|
|
64
|
+
raise UnknownRequestStopped, "#{request_id} is not in the ongoing requests list"
|
|
85
65
|
end
|
|
86
66
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def schedule_query(args)
|
|
90
|
-
uuid = SecureRandom.uuid
|
|
91
|
-
queue = Async::Queue.new
|
|
92
|
-
@requests[uuid] = queue
|
|
93
|
-
@query_queue << to_query(uuid, args)
|
|
94
|
-
|
|
95
|
-
[queue, uuid]
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def to_query(uuid, message)
|
|
99
|
-
case message.first
|
|
67
|
+
def to_query(request_id, message)
|
|
68
|
+
case message.first # TODO: properly handle unknown type of message
|
|
100
69
|
when String
|
|
101
|
-
string_query_message(
|
|
70
|
+
string_query_message(request_id, *message)
|
|
102
71
|
when Grumlin::Step
|
|
103
|
-
|
|
72
|
+
bytecode_query_message(request_id, Translator.to_bytecode_query(message))
|
|
104
73
|
end
|
|
105
74
|
end
|
|
106
75
|
|
|
107
|
-
def check_errors!(status,
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
end
|
|
76
|
+
def check_errors!(_request_id, status, response)
|
|
77
|
+
reraise_error!(response) if status == :error
|
|
78
|
+
|
|
79
|
+
status = response[:status]
|
|
112
80
|
|
|
113
|
-
|
|
114
|
-
|
|
81
|
+
if (error = ERRORS[status[:code]])
|
|
82
|
+
raise(error, status)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return unless SUCCESS[status[:code]].nil?
|
|
86
|
+
|
|
87
|
+
raise(UnknownResponseStatus, status)
|
|
115
88
|
end
|
|
116
89
|
|
|
117
90
|
def reraise_error!(error)
|
|
118
91
|
raise error
|
|
119
92
|
rescue StandardError
|
|
120
|
-
raise
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def query_task(connection)
|
|
124
|
-
loop do
|
|
125
|
-
connection.write @query_queue.dequeue
|
|
126
|
-
connection.flush
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def response_task(connection)
|
|
131
|
-
loop do
|
|
132
|
-
response = connection.read
|
|
133
|
-
response_queue = @requests[response[:requestId]]
|
|
134
|
-
response_queue << [:response, response]
|
|
135
|
-
end
|
|
93
|
+
raise UnknownError
|
|
136
94
|
end
|
|
137
95
|
|
|
138
|
-
def string_query_message(
|
|
96
|
+
def string_query_message(request_id, query, bindings)
|
|
139
97
|
{
|
|
140
|
-
requestId:
|
|
98
|
+
requestId: request_id,
|
|
141
99
|
op: "eval",
|
|
142
100
|
processor: "",
|
|
143
101
|
args: {
|
|
@@ -148,25 +106,16 @@ module Grumlin
|
|
|
148
106
|
}
|
|
149
107
|
end
|
|
150
108
|
|
|
151
|
-
def bytecode_query_message(
|
|
109
|
+
def bytecode_query_message(request_id, bytecode)
|
|
152
110
|
{
|
|
153
|
-
requestId:
|
|
111
|
+
requestId: request_id,
|
|
154
112
|
op: "bytecode",
|
|
155
113
|
processor: "traversal",
|
|
156
114
|
args: {
|
|
157
|
-
gremlin:
|
|
115
|
+
gremlin: Typing.to_bytecode(bytecode),
|
|
158
116
|
aliases: { g: :g }
|
|
159
117
|
}
|
|
160
118
|
}
|
|
161
119
|
end
|
|
162
|
-
|
|
163
|
-
def build_query(uuid, steps)
|
|
164
|
-
case @mode
|
|
165
|
-
when :string
|
|
166
|
-
string_query_message(uuid, *Translator.to_string_query(steps))
|
|
167
|
-
else
|
|
168
|
-
bytecode_query_message(uuid, Translator.to_bytecode_query(steps))
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
120
|
end
|
|
172
121
|
end
|
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
|
data/lib/grumlin/step.rb
CHANGED
|
@@ -1,46 +1,31 @@
|
|
|
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
|
-
# TODO: add support for bytecode
|
|
8
7
|
def initialize(client, name, *args, previous_steps: [])
|
|
8
|
+
super(name, *args, previous_steps: previous_steps)
|
|
9
9
|
@client = client
|
|
10
|
-
@name = name
|
|
11
|
-
@previous_steps = previous_steps
|
|
12
|
-
@args = args
|
|
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
|
-
end
|
|
30
|
-
|
|
31
|
-
def inspect
|
|
32
|
-
"<Step #{self}>" # TODO: substitute bindings
|
|
18
|
+
@toList ||= @client.submit(*steps) # rubocop:disable Naming/VariableName
|
|
33
19
|
end
|
|
34
20
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Translator.to_string(steps)
|
|
21
|
+
def iterate
|
|
22
|
+
@client.submit(*(steps + [nil]))
|
|
38
23
|
end
|
|
39
24
|
|
|
40
|
-
|
|
25
|
+
private
|
|
41
26
|
|
|
42
|
-
def
|
|
43
|
-
(@previous_steps
|
|
27
|
+
def add_step(step_name, args, previous_steps:)
|
|
28
|
+
self.class.new(@client, step_name, *args, previous_steps: previous_steps)
|
|
44
29
|
end
|
|
45
30
|
end
|
|
46
31
|
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,22 @@
|
|
|
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
|
+
expect(Grumlin.config.default_client.requests).to be_empty
|
|
16
|
+
Grumlin.config.default_client.disconnect
|
|
17
|
+
Grumlin.config.reset!
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
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,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Grumlin
|
|
4
|
+
module Transport
|
|
5
|
+
# A transport based on https://github.com/socketry/async
|
|
6
|
+
# and https://github.com/socketry/async-websocket
|
|
7
|
+
class Async
|
|
8
|
+
attr_reader :requests
|
|
9
|
+
|
|
10
|
+
def initialize(url, task: ::Async::Task.current)
|
|
11
|
+
@task = task
|
|
12
|
+
@endpoint = ::Async::HTTP::Endpoint.parse(url)
|
|
13
|
+
|
|
14
|
+
@requests = {}
|
|
15
|
+
@query_queue = ::Async::Queue.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def connect
|
|
19
|
+
raise AlreadyConnectedError if connected?
|
|
20
|
+
|
|
21
|
+
@client = ::Async::WebSocket::Client.open(@endpoint)
|
|
22
|
+
@connection = @client.connect(@endpoint.authority, @endpoint.path)
|
|
23
|
+
|
|
24
|
+
@tasks_barrier = ::Async::Barrier.new(parent: @task)
|
|
25
|
+
|
|
26
|
+
@tasks_barrier.async { query_task }
|
|
27
|
+
@tasks_barrier.async { response_task }
|
|
28
|
+
rescue StandardError
|
|
29
|
+
raise ConnectionError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def disconnect
|
|
33
|
+
raise NotConnectedError unless connected?
|
|
34
|
+
|
|
35
|
+
@tasks_barrier.tasks.each(&:stop)
|
|
36
|
+
@tasks_barrier.wait
|
|
37
|
+
|
|
38
|
+
@connection.close
|
|
39
|
+
@client.close
|
|
40
|
+
|
|
41
|
+
@client = nil
|
|
42
|
+
@connection = nil
|
|
43
|
+
@tasks_barrier = nil
|
|
44
|
+
|
|
45
|
+
raise ResourceLeakError, "ongoing requests list is not empty: #{@requests.count} items" unless @requests.empty?
|
|
46
|
+
raise ResourceLeakError, "query queue empty: #{@query.count} items" unless @query_queue.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Raw message
|
|
50
|
+
def submit(message)
|
|
51
|
+
raise NotConnectedError unless connected?
|
|
52
|
+
|
|
53
|
+
uuid = message[:requestId]
|
|
54
|
+
::Async::Queue.new.tap do |queue|
|
|
55
|
+
@requests[uuid] = queue
|
|
56
|
+
@query_queue << message
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def close_request(request_id)
|
|
61
|
+
@requests.delete(request_id)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ongoing_request?(request_id)
|
|
65
|
+
@requests.key?(request_id)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def connected?
|
|
69
|
+
!@connection.nil?
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def query_task
|
|
75
|
+
@query_queue.each do |query|
|
|
76
|
+
@connection.write(query)
|
|
77
|
+
@connection.flush
|
|
78
|
+
end
|
|
79
|
+
rescue StandardError
|
|
80
|
+
raise DisconnectError
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def response_task
|
|
84
|
+
loop do
|
|
85
|
+
response = @connection.read
|
|
86
|
+
# TODO: sometimes response does not include requestID, no idea how to handle it so far.
|
|
87
|
+
response_queue = @requests[response[:requestId]]
|
|
88
|
+
response_queue << [:response, response]
|
|
89
|
+
end
|
|
90
|
+
rescue StandardError
|
|
91
|
+
raise DisconnectError
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/grumlin/traversal.rb
CHANGED
|
@@ -4,18 +4,15 @@ module Grumlin
|
|
|
4
4
|
class Traversal
|
|
5
5
|
attr_reader :connection
|
|
6
6
|
|
|
7
|
-
def initialize(client_or_url
|
|
7
|
+
def initialize(client_or_url = Grumlin.config.default_client)
|
|
8
8
|
@client = if client_or_url.is_a?(String)
|
|
9
9
|
Grumlin::Client.new(client_or_url)
|
|
10
10
|
else
|
|
11
11
|
client_or_url
|
|
12
12
|
end
|
|
13
|
-
|
|
14
|
-
return if block.nil?
|
|
15
|
-
|
|
16
|
-
TraversingContext.new(self).instance_exec(&block)
|
|
17
13
|
end
|
|
18
14
|
|
|
15
|
+
# TODO: add other start steps
|
|
19
16
|
%w[addV addE V E].each do |step|
|
|
20
17
|
define_method step do |*args|
|
|
21
18
|
Step.new(@client, step, *args)
|
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
data/lib/grumlin/vertex.rb
CHANGED
|
@@ -10,11 +10,11 @@ module Grumlin
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def ==(other)
|
|
13
|
-
@label == other.label && @id == other.id
|
|
13
|
+
self.class == other.class && @label == other.label && @id == other.id
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def inspect
|
|
17
|
-
"
|
|
17
|
+
"v[#{@id}]"
|
|
18
18
|
end
|
|
19
19
|
alias to_s inspect
|
|
20
20
|
end
|
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.
|
|
4
|
+
version: 0.2.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: 2021-
|
|
11
|
+
date: 2021-07-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: async-websocket
|
|
@@ -16,14 +16,14 @@ dependencies:
|
|
|
16
16
|
requirements:
|
|
17
17
|
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0.
|
|
19
|
+
version: '0.19'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0.
|
|
26
|
+
version: '0.19'
|
|
27
27
|
description: Gremlin query language DSL for Ruby.
|
|
28
28
|
email:
|
|
29
29
|
- zhulik.gleb@gmail.com
|
|
@@ -33,6 +33,7 @@ extra_rdoc_files: []
|
|
|
33
33
|
files:
|
|
34
34
|
- ".github/workflows/main.yml"
|
|
35
35
|
- ".gitignore"
|
|
36
|
+
- ".overcommit.yml"
|
|
36
37
|
- ".rspec"
|
|
37
38
|
- ".rubocop.yml"
|
|
38
39
|
- CHANGELOG.md
|
|
@@ -43,20 +44,30 @@ files:
|
|
|
43
44
|
- README.md
|
|
44
45
|
- bin/console
|
|
45
46
|
- bin/setup
|
|
46
|
-
- bin/stress
|
|
47
47
|
- docker-compose.yml
|
|
48
48
|
- gremlin_server/Dockerfile
|
|
49
49
|
- gremlin_server/tinkergraph-empty.properties
|
|
50
50
|
- grumlin.gemspec
|
|
51
51
|
- lib/grumlin.rb
|
|
52
|
+
- lib/grumlin/anonymous_step.rb
|
|
52
53
|
- lib/grumlin/client.rb
|
|
53
54
|
- lib/grumlin/edge.rb
|
|
54
55
|
- lib/grumlin/exceptions.rb
|
|
56
|
+
- lib/grumlin/order.rb
|
|
57
|
+
- lib/grumlin/p.rb
|
|
58
|
+
- lib/grumlin/path.rb
|
|
59
|
+
- lib/grumlin/pop.rb
|
|
55
60
|
- lib/grumlin/step.rb
|
|
61
|
+
- lib/grumlin/sugar.rb
|
|
62
|
+
- lib/grumlin/t.rb
|
|
63
|
+
- lib/grumlin/test/rspec.rb
|
|
64
|
+
- lib/grumlin/test/rspec/db_cleaner_context.rb
|
|
65
|
+
- lib/grumlin/test/rspec/gremlin_context.rb
|
|
56
66
|
- lib/grumlin/translator.rb
|
|
67
|
+
- lib/grumlin/transport/async.rb
|
|
57
68
|
- lib/grumlin/traversal.rb
|
|
58
|
-
- lib/grumlin/traversing_context.rb
|
|
59
69
|
- lib/grumlin/typing.rb
|
|
70
|
+
- lib/grumlin/u.rb
|
|
60
71
|
- lib/grumlin/version.rb
|
|
61
72
|
- lib/grumlin/vertex.rb
|
|
62
73
|
homepage: https://github.com/zhulik/grumlin
|
|
@@ -81,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
81
92
|
- !ruby/object:Gem::Version
|
|
82
93
|
version: '0'
|
|
83
94
|
requirements: []
|
|
84
|
-
rubygems_version: 3.2.
|
|
95
|
+
rubygems_version: 3.2.22
|
|
85
96
|
signing_key:
|
|
86
97
|
specification_version: 4
|
|
87
98
|
summary: Gremlin query language DSL for Ruby.
|
data/bin/stress
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "bundler/setup"
|
|
5
|
-
require "grumlin"
|
|
6
|
-
require "irb"
|
|
7
|
-
|
|
8
|
-
def queries(client, uuids)
|
|
9
|
-
total = 0
|
|
10
|
-
g = Grumlin::Traversal.new(client)
|
|
11
|
-
|
|
12
|
-
loop do
|
|
13
|
-
uuid = uuids.sample
|
|
14
|
-
result = g.V(uuid).toList[0]
|
|
15
|
-
raise "!!!" if result.id != uuid
|
|
16
|
-
|
|
17
|
-
total += 1
|
|
18
|
-
end
|
|
19
|
-
rescue Async::Stop
|
|
20
|
-
total
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def prepare_dataset(client)
|
|
24
|
-
uuids = Array.new(1000) { SecureRandom.uuid }
|
|
25
|
-
|
|
26
|
-
Grumlin::Traversal.new(client) do
|
|
27
|
-
g.V().drop
|
|
28
|
-
|
|
29
|
-
uuids.each do |uuid|
|
|
30
|
-
g.addV("test_vertex").property(id, uuid).toList
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
uuids
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
Async do |task|
|
|
38
|
-
client = Grumlin::Client.new("ws://localhost:8182/gremlin", mode: :bytecode)
|
|
39
|
-
|
|
40
|
-
uuids = prepare_dataset(client)
|
|
41
|
-
tasks = Array.new(20) { task.async { queries(client, uuids) } }
|
|
42
|
-
|
|
43
|
-
task.sleep(60)
|
|
44
|
-
|
|
45
|
-
tasks.each(&:stop)
|
|
46
|
-
total = tasks.sum(&:wait)
|
|
47
|
-
|
|
48
|
-
p("#{total} requests performed")
|
|
49
|
-
ensure
|
|
50
|
-
client.disconnect
|
|
51
|
-
end
|