grumlin 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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