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.
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
@@ -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
@@ -4,21 +4,14 @@ module Grumlin
4
4
  class Traversal
5
5
  attr_reader :connection
6
6
 
7
- def initialize(client_or_url, &block)
8
- @client = if client_or_url.is_a?(String)
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(@client, step, *args)
14
+ Step.new(@pool, step, *args)
22
15
  end
23
16
  end
24
17
 
@@ -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:Traverser" => ->(value) { cast(value[:value]) } # TODO: wtf is bulk?
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(&:to_sym).transform_values { |v| cast(v) }
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end