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.
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
- "<E #{@label}(#{@id})>"
22
+ "e[#{@id}][#{@inV}-#{@label}->#{@outV}]"
23
23
  end
24
24
  alias to_s inspect
25
25
  end
@@ -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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Path
5
+ def initialize(path)
6
+ @labels = Typing.cast(path[:labels])
7
+ @objects = Typing.cast(path[:objects])
8
+ end
9
+
10
+ def inspect
11
+ "p[#{@objects}]"
12
+ end
13
+ alias to_s inspect
14
+ end
15
+ end
@@ -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, :name, :args
4
+ class Step < AnonymousStep
5
+ attr_reader :client
6
6
 
7
- # TODO: add support for bytecode
8
- def initialize(client, name, *args, previous_steps: [])
9
- @client = client
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
- %w[addV addE V E limit count drop property valueMap select from to as].each do |step|
16
- define_method step do |*args|
17
- Step.new(@client, step, *args, previous_steps: steps)
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.query(*steps)
29
- end
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
- # TODO: memoization
36
- def to_s(*)
37
- Translator.to_string(steps)
23
+ def iterate
24
+ @pool.acquire do |client|
25
+ client.write(*(steps + [nil]))
26
+ end
38
27
  end
39
28
 
40
- alias to_gremlin to_s
29
+ private
41
30
 
42
- def steps
43
- (@previous_steps + [self])
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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "grumlin/test/rspec/db_cleaner_context"
4
+ require "grumlin/test/rspec/gremlin_context"
5
+
6
+ module Grumlin
7
+ module Test
8
+ module RSpec
9
+ end
10
+ end
11
+ 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
@@ -3,38 +3,39 @@
3
3
  module Grumlin
4
4
  module Translator
5
5
  class << self
6
- def to_string_query(steps) # rubocop:disable Metrics/MethodLength
7
- counter = 0
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
- ["g.#{string_steps.join(".")}", bindings]
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
- [step.name, *step.args.flatten]
16
+ arg_to_query_bytecode(step)
25
17
  end
26
18
  end
27
19
 
28
- def to_string(steps)
29
- "g." + steps.map do |step| # rubocop:disable Style/StringConcatenation
30
- "#{step.name}(#{step.args.map(&:inspect).join(", ")})"
31
- end.join(".")
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
- private
31
+ def arg_to_query_bytecode(arg)
32
+ return ["none"] if arg.nil?
33
+ return arg unless arg.is_a?(AnonymousStep)
35
34
 
36
- def binding_name(num)
37
- "b_#{num.to_s(16)}"
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