grumlin 0.5.1 → 0.7.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: 5fb5b809483aeed3b56aeafc11e0f08919a68ce420a14ae918e3aecab791e266
4
- data.tar.gz: e42c886a034faeb5ca512882811e78a39f1f5f386b697b0f4061c5be5d5a5f0b
3
+ metadata.gz: 4260297c3d75ec7bc1f987d8c7bad4efd75c81627814fdf984a0aaac0045332b
4
+ data.tar.gz: 1cb521906a08cf5390f058a28f380492a2999e55ef023c473fc6bae64533b53b
5
5
  SHA512:
6
- metadata.gz: 2bd8e23cd4e6ab7526e2e4aaf92dddfa2b1e0dc51fd2e15e9e0a09541e9716c741673a8e6f85b077e5fd1a3a5f16519ae533748347b1aff0ba5de47a8394ed47
7
- data.tar.gz: 673985aeb319810c061234e1d0964cf6e40b95363288423d7cda4e0932a7e271d51b957dccc5605a160e54e680d2a869bfa32769b393d19387e61e0ad15b7794
6
+ metadata.gz: 992d2192402f3fe282203a3fd0eff1e6c886470899cb37f00ca2abf1392e4d946c7391be0eb7d6560c9941f7a17a3552682a8a47f56273f4299c06551c1e9ffc
7
+ data.tar.gz: d8695e2485915cb7b4d37a755e42d20b6d7f575d906ae5a0aca5f508c01a104c08f7bb51a2285c9dac48a0091771882afeb405fd26e7802c4d48513274e2895f
data/.overcommit.yml CHANGED
@@ -3,6 +3,3 @@ PreCommit:
3
3
  enabled: true
4
4
  on_warn: fail # Treat all warnings as failures
5
5
 
6
- PrePush:
7
- RSpec:
8
- enabled: true
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.6.6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.5.1)
4
+ grumlin (0.7.0)
5
5
  async-pool (~> 0.3)
6
6
  async-websocket (~> 0.19)
7
7
 
@@ -70,7 +70,7 @@ GEM
70
70
  ast (~> 2.4.1)
71
71
  protocol-hpack (1.4.2)
72
72
  protocol-http (0.22.5)
73
- protocol-http1 (0.14.1)
73
+ protocol-http1 (0.14.2)
74
74
  protocol-http (~> 0.22)
75
75
  protocol-http2 (0.14.2)
76
76
  protocol-hpack (~> 1.4)
@@ -166,4 +166,4 @@ DEPENDENCIES
166
166
  solargraph
167
167
 
168
168
  BUNDLED WITH
169
- 2.2.15
169
+ 2.2.26
data/bin/console CHANGED
@@ -10,7 +10,7 @@ Grumlin.configure do |config|
10
10
  end
11
11
 
12
12
  Async do
13
- g = Grumlin::Traversal.new
13
+ include Grumlin::Sugar
14
14
 
15
15
  IRB.setup(nil)
16
16
  workspace = IRB::WorkSpace.new(binding)
data/lib/async/channel.rb CHANGED
@@ -32,7 +32,7 @@ module Async
32
32
  end
33
33
 
34
34
  def close
35
- raise(ChannelClosedError, "Cannot close a closed channel") if closed?
35
+ return if closed?
36
36
 
37
37
  @queue << [:close]
38
38
  @closed = true
@@ -4,23 +4,23 @@ module Grumlin
4
4
  class AnonymousStep
5
5
  attr_reader :name, :args
6
6
 
7
+ # TODO: add other steps
8
+ SUPPORTED_STEPS = %w[E V addE addV as by coalesce count dedup drop elementMap emit fold from group groupCount has
9
+ hasId hasLabel hasNot in inV label limit not order out outE path project property repeat select
10
+ to unfold valueMap values where].freeze
11
+
7
12
  def initialize(name, *args, previous_steps: [])
8
13
  @name = name
9
14
  @previous_steps = previous_steps
10
15
  @args = args
11
16
  end
12
17
 
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|
18
+ SUPPORTED_STEPS.each do |step|
16
19
  define_method step do |*args|
17
20
  add_step(step, args, previous_steps: steps)
18
21
  end
19
22
  end
20
23
 
21
- alias addVertex addV
22
- alias addEdge addE
23
-
24
24
  def inspect
25
25
  @inspect ||= to_bytecode.to_s
26
26
  end
@@ -13,6 +13,7 @@ module Grumlin
13
13
  def initialize(url, client_factory:, concurrency: 1, parent: Async::Task.current)
14
14
  super(concurrency)
15
15
  @client = client_factory.call(url, parent).tap(&:connect)
16
+ @parent = parent
16
17
  end
17
18
 
18
19
  def closed?
@@ -25,34 +26,67 @@ module Grumlin
25
26
 
26
27
  def write(*args)
27
28
  @client.write(*args)
29
+ ensure
30
+ @count += 1
31
+ end
32
+
33
+ def viable?
34
+ !closed?
35
+ end
36
+
37
+ def reusable?
38
+ !closed?
28
39
  end
29
40
  end
30
41
 
42
+ include Console
43
+
44
+ # Client is not reusable. Once closed should be recreated.
31
45
  def initialize(url, parent: Async::Task.current, **client_options)
32
46
  @url = url
33
47
  @client_options = client_options
34
48
  @parent = parent
35
- reset!
49
+ @request_dispatcher = nil
50
+ @transport = nil
36
51
  end
37
52
 
38
53
  def connect
54
+ raise "ClientClosed" if @closed
55
+
39
56
  @transport = build_transport
40
57
  response_channel = @transport.connect
41
58
  @request_dispatcher = RequestDispatcher.new
42
- @parent.async do
59
+ @response_task = @parent.async do
43
60
  response_channel.each do |response|
44
61
  @request_dispatcher.add_response(response)
45
62
  end
46
- rescue StandardError
47
- close
63
+ rescue Async::Stop, Async::TimeoutError, StandardError
64
+ close(check_requests: false)
48
65
  end
66
+ logger.debug(self, "Connected")
49
67
  end
50
68
 
51
- def close
52
- @transport.close
53
- raise ResourceLeakError, "Request list is not empty: #{requests}" if @request_dispatcher.requests.any?
69
+ # Before calling close the user must ensure that:
70
+ # 1) There are no ongoing requests
71
+ # 2) There will be no new writes after
72
+ def close(check_requests: true) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
73
+ return if @closed
74
+
75
+ @closed = true
54
76
 
55
- reset!
77
+ @transport&.close
78
+ @transport&.wait
79
+
80
+ @response_task&.stop
81
+ @response_task&.wait
82
+
83
+ return if @request_dispatcher&.requests&.empty?
84
+
85
+ @request_dispatcher.clear unless check_requests
86
+
87
+ raise ResourceLeakError, "Request list is not empty: #{@request_dispatcher.requests}" if check_requests
88
+ ensure
89
+ logger.debug(self, "Closed")
56
90
  end
57
91
 
58
92
  def connected?
@@ -70,9 +104,9 @@ module Grumlin
70
104
 
71
105
  begin
72
106
  channel.dequeue.flat_map { |item| Typing.cast(item) }
73
- rescue Async::Stop
74
- retry if @request_dispatcher.ongoing_request?(request_id)
75
- raise Grumlin::UnknownRequestStoppedError, "#{request_id} is not in the ongoing requests list"
107
+ rescue Async::Stop, Async::TimeoutError
108
+ close(check_requests: false)
109
+ raise
76
110
  end
77
111
  end
78
112
 
@@ -96,11 +130,6 @@ module Grumlin
96
130
  }
97
131
  end
98
132
 
99
- def reset!
100
- @request_dispatcher = nil
101
- @transport = nil
102
- end
103
-
104
133
  def build_transport
105
134
  Transport.new(@url, parent: @parent, **@client_options)
106
135
  end
data/lib/grumlin/order.rb CHANGED
@@ -2,16 +2,15 @@
2
2
 
3
3
  module Grumlin
4
4
  module Order
5
+ # TODO: share the code?
5
6
  class << self
6
- DESC = { "@type": "g:Order", "@value": "desc" }.freeze
7
- ASC = { "@type": "g:Order", "@value": "asc" }.freeze
7
+ %i[asc desc].each do |step|
8
+ define_method step do
9
+ name = "@#{step}"
10
+ return instance_variable_get(name) if instance_variable_defined?(name)
8
11
 
9
- def asc
10
- ASC
11
- end
12
-
13
- def desc
14
- DESC
12
+ instance_variable_set(name, TypedValue.new("Order", step))
13
+ end
15
14
  end
16
15
  end
17
16
  end
data/lib/grumlin/p.rb CHANGED
@@ -5,7 +5,7 @@ module Grumlin
5
5
  module P
6
6
  %w[within].each do |step|
7
7
  define_method step do |*args|
8
- { # TODO: replace with a class?
8
+ { # TODO: replace with a TypedValue?
9
9
  "@type": "g:P",
10
10
  "@value": { predicate: "within", value: { "@type": "g:List", "@value": args } }
11
11
  }
data/lib/grumlin/pop.rb CHANGED
@@ -2,26 +2,15 @@
2
2
 
3
3
  module Grumlin
4
4
  module Pop
5
+ # TODO: share the code?
5
6
  class << self
6
- FIRST = { "@type": "g:Pop", "@value": "first" }.freeze
7
- LAST = { "@type": "g:Pop", "@value": "last" }.freeze
8
- ALL = { "@type": "g:Pop", "@value": "all" }.freeze
9
- MIXED = { "@type": "g:Pop", "@value": "mixed" }.freeze
7
+ %i[first last all mixed].each do |step|
8
+ define_method step do
9
+ name = "@#{step}"
10
+ return instance_variable_get(name) if instance_variable_defined?(name)
10
11
 
11
- def first
12
- FIRST
13
- end
14
-
15
- def last
16
- LAST
17
- end
18
-
19
- def all
20
- ALL
21
- end
22
-
23
- def mixed
24
- MIXED
12
+ instance_variable_set(name, TypedValue.new("Pop", step))
13
+ end
25
14
  end
26
15
  end
27
16
  end
@@ -22,6 +22,8 @@ module Grumlin
22
22
  498 => ClientSideError
23
23
  }.freeze
24
24
 
25
+ include Console
26
+
25
27
  def initialize
26
28
  @requests = {}
27
29
  end
@@ -69,6 +71,10 @@ module Grumlin
69
71
  @requests.key?(request_id)
70
72
  end
71
73
 
74
+ def clear
75
+ @requests.clear
76
+ end
77
+
72
78
  private
73
79
 
74
80
  def check_errors!(status)
data/lib/grumlin/t.rb CHANGED
@@ -2,16 +2,15 @@
2
2
 
3
3
  module Grumlin
4
4
  module T
5
+ # TODO: share the code?
5
6
  class << self
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?
7
+ %i[id label].each do |step|
8
+ define_method step do
9
+ name = "@#{step}"
10
+ return instance_variable_get(name) if instance_variable_defined?(name)
8
11
 
9
- def id
10
- T_ID
11
- end
12
-
13
- def label
14
- T_LABEL
12
+ instance_variable_set(name, TypedValue.new("T", step))
13
+ end
15
14
  end
16
15
  end
17
16
  end
@@ -20,6 +20,7 @@ module Grumlin
20
20
  private
21
21
 
22
22
  def arg_to_bytecode(arg)
23
+ return arg.to_bytecode if arg.is_a?(TypedValue)
23
24
  return arg unless arg.is_a?(AnonymousStep)
24
25
 
25
26
  args = arg.args.flatten.map do |a|
@@ -30,8 +31,11 @@ module Grumlin
30
31
 
31
32
  def arg_to_query_bytecode(arg)
32
33
  return ["none"] if arg.nil?
34
+ return arg.to_bytecode if arg.is_a?(TypedValue)
33
35
  return arg unless arg.is_a?(AnonymousStep)
34
36
 
37
+ # return arg.to_bytecode if arg.is_a?(TypedValue)
38
+
35
39
  args = arg.args.flatten.map do |a|
36
40
  a.instance_of?(AnonymousStep) ? Typing.to_bytecode(to_bytecode(a.steps)) : arg_to_query_bytecode(a)
37
41
  end
@@ -5,47 +5,33 @@ module Grumlin
5
5
  # A transport based on https://github.com/socketry/async
6
6
  # and https://github.com/socketry/async-websocket
7
7
 
8
+ include Console
9
+
8
10
  attr_reader :url
9
11
 
12
+ # Transport is not reusable. Once closed should be recreated.
10
13
  def initialize(url, parent: Async::Task.current, **client_options)
11
14
  @url = url
12
15
  @parent = parent
13
16
  @client_options = client_options
14
17
  @request_channel = Async::Channel.new
15
18
  @response_channel = Async::Channel.new
16
- reset!
17
19
  end
18
20
 
19
21
  def connected?
20
- @connected
22
+ !@connection.nil?
21
23
  end
22
24
 
23
- def connect # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25
+ def connect
26
+ raise "ClientClosed" if @closed
24
27
  raise AlreadyConnectedError if connected?
25
28
 
26
29
  @connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
30
+ logger.debug(self) { "Connected to #{@url}." }
27
31
 
28
- @response_task = @parent.async do
29
- loop do
30
- data = @connection.read
31
- @response_channel << data
32
- end
33
- rescue Async::Stop
34
- @response_channel.close
35
- rescue StandardError => e
36
- @response_channel.exception(e)
37
- end
32
+ @response_task = @parent.async { run_response_task }
38
33
 
39
- @request_task = @parent.async do
40
- @request_channel.each do |message|
41
- @connection.write(message)
42
- @connection.flush
43
- end
44
- rescue StandardError => e
45
- @response_channel.exception(e)
46
- end
47
-
48
- @connected = true
34
+ @request_task = @parent.async { run_request_task }
49
35
 
50
36
  @response_channel
51
37
  end
@@ -57,30 +43,59 @@ module Grumlin
57
43
  end
58
44
 
59
45
  def close
60
- return unless connected?
46
+ return if @closed
61
47
 
62
- @request_channel.close
63
- @request_task.wait
48
+ @closed = true
64
49
 
65
- @response_task.stop
66
- @response_task.wait
50
+ @request_channel.close
51
+ @response_channel.close
67
52
 
68
53
  begin
69
54
  @connection.close
70
- rescue Errno::EPIPE
55
+ rescue StandardError
71
56
  nil
72
57
  end
58
+ @connection = nil
59
+
60
+ @request_task&.stop(true)
61
+ @response_task&.stop(true)
62
+ end
73
63
 
74
- reset!
64
+ def wait
65
+ @request_task.wait
66
+ @response_task.wait
75
67
  end
76
68
 
77
69
  private
78
70
 
79
- def reset!
80
- @connected = false
81
- @connection = nil
82
- @response_task = nil
83
- @request_task = nil
71
+ def run_response_task
72
+ with_guard do
73
+ loop do
74
+ data = @connection.read
75
+ @response_channel << data
76
+ end
77
+ end
78
+ end
79
+
80
+ def run_request_task
81
+ with_guard do
82
+ @request_channel.each do |message|
83
+ @connection.write(message)
84
+ @connection.flush
85
+ end
86
+ end
87
+ end
88
+
89
+ def with_guard
90
+ yield
91
+ rescue Async::Stop, Async::TimeoutError, StandardError => e
92
+ logger.debug(self) { "Guard error, closing." }
93
+ begin
94
+ @response_channel.exception(e)
95
+ rescue Async::Channel::ChannelClosedError
96
+ nil
97
+ end
98
+ close
84
99
  end
85
100
  end
86
101
  end
@@ -4,18 +4,17 @@ module Grumlin
4
4
  class Traversal
5
5
  attr_reader :connection
6
6
 
7
+ # TODO: add other start steps
8
+ SUPPORTED_START_STEPS = %w[E V addE addV].freeze
9
+
7
10
  def initialize(pool = Grumlin.config.default_pool)
8
11
  @pool = pool
9
12
  end
10
13
 
11
- # TODO: add other start steps
12
- %w[addV addE V E].each do |step|
14
+ SUPPORTED_START_STEPS.each do |step|
13
15
  define_method step do |*args|
14
16
  Step.new(@pool, step, *args)
15
17
  end
16
18
  end
17
-
18
- alias addVertex addV
19
- alias addEdge addE
20
19
  end
21
20
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ # TODO: find a better name
5
+ class TypedValue
6
+ def initialize(type, value)
7
+ @type = type
8
+ @value = value
9
+ end
10
+
11
+ def inspect(*)
12
+ "#{@type}.#{@value}"
13
+ end
14
+
15
+ def to_bytecode
16
+ @to_bytecode ||= { "@type": "g:#{@type}", "@value": @value }
17
+ end
18
+ end
19
+ end
data/lib/grumlin/u.rb CHANGED
@@ -2,8 +2,11 @@
2
2
 
3
3
  module Grumlin
4
4
  module U
5
+ # TODO: add other start steps
6
+ SUPPORTED_START_STEPS = %w[V addV count has out unfold values].freeze
7
+
5
8
  class << self
6
- %w[addV V has count out values unfold].each do |step|
9
+ SUPPORTED_START_STEPS.each do |step|
7
10
  define_method step do |*args|
8
11
  AnonymousStep.new(step, *args)
9
12
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/grumlin.rb CHANGED
@@ -19,6 +19,7 @@ require_relative "grumlin/exceptions"
19
19
 
20
20
  require_relative "grumlin/transport"
21
21
  require_relative "grumlin/client"
22
+ require_relative "grumlin/typed_value"
22
23
 
23
24
  require_relative "grumlin/vertex"
24
25
  require_relative "grumlin/edge"
@@ -42,10 +43,9 @@ module Grumlin
42
43
  class Config
43
44
  attr_accessor :url, :pool_size, :client_concurrency, :client_factory
44
45
 
45
- # For some reason, client_concurrency must be greater than pool_size
46
46
  def initialize
47
47
  @pool_size = 10
48
- @client_concurrency = 20
48
+ @client_concurrency = 5
49
49
  @client_factory = ->(url, parent) { Grumlin::Client.new(url, parent: parent) }
50
50
  end
51
51
 
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.5.1
4
+ version: 0.7.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-08-23 00:00:00.000000000 Z
11
+ date: 2021-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool
@@ -50,6 +50,7 @@ files:
50
50
  - ".overcommit.yml"
51
51
  - ".rspec"
52
52
  - ".rubocop.yml"
53
+ - ".tool-versions"
53
54
  - CHANGELOG.md
54
55
  - CODE_OF_CONDUCT.md
55
56
  - Gemfile
@@ -82,6 +83,7 @@ files:
82
83
  - lib/grumlin/translator.rb
83
84
  - lib/grumlin/transport.rb
84
85
  - lib/grumlin/traversal.rb
86
+ - lib/grumlin/typed_value.rb
85
87
  - lib/grumlin/typing.rb
86
88
  - lib/grumlin/u.rb
87
89
  - lib/grumlin/version.rb