grumlin 0.1.3 → 0.5.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
19
- end
20
-
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
- def toList # rubocop:disable Naming/MethodName
28
- @client.query(*steps)
12
+ def next
13
+ @enum ||= toList.to_enum
14
+ @enum.next
29
15
  end
30
16
 
31
- def inspect
32
- "<Step #{self}>" # TODO: substitute bindings
17
+ def toList
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,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Sugar
5
+ HELPERS = [
6
+ Grumlin::U,
7
+ Grumlin::T,
8
+ Grumlin::P,
9
+ Grumlin::Pop,
10
+ Grumlin::Order
11
+ ].freeze
12
+
13
+ def self.included(base)
14
+ HELPERS.each do |helper|
15
+ name = helper.name.split("::").last
16
+ base.const_set(name, helper)
17
+ end
18
+ end
19
+
20
+ def __
21
+ Grumlin::U
22
+ end
23
+
24
+ def g
25
+ Grumlin::Traversal.new
26
+ end
27
+ end
28
+ end
data/lib/grumlin/t.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module T
5
+ 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?
8
+
9
+ def id
10
+ T_ID
11
+ end
12
+
13
+ def label
14
+ T_LABEL
15
+ end
16
+ end
17
+ end
18
+ 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,27 @@
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
+ include Grumlin::Sugar
12
+
13
+ before do
14
+ Grumlin::Sugar::HELPERS.each do |helper|
15
+ name = helper.name.split("::").last
16
+ stub_const(name, helper)
17
+ end
18
+ end
19
+
20
+ after do
21
+ Grumlin.config.default_pool.close
22
+ Grumlin.config.reset!
23
+ end
24
+ end
25
+ end
26
+ end
27
+ 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,86 @@
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
+
8
+ attr_reader :url
9
+
10
+ def initialize(url, parent: Async::Task.current, **client_options)
11
+ @url = url
12
+ @parent = parent
13
+ @client_options = client_options
14
+ @request_channel = Async::Channel.new
15
+ @response_channel = Async::Channel.new
16
+ reset!
17
+ end
18
+
19
+ def connected?
20
+ @connected
21
+ end
22
+
23
+ def connect # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
24
+ raise AlreadyConnectedError if connected?
25
+
26
+ @connection = Async::WebSocket::Client.connect(Async::HTTP::Endpoint.parse(@url), **@client_options)
27
+
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
38
+
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
49
+
50
+ @response_channel
51
+ end
52
+
53
+ def write(message)
54
+ raise NotConnectedError unless connected?
55
+
56
+ @request_channel << message
57
+ end
58
+
59
+ def close
60
+ return unless connected?
61
+
62
+ @request_channel.close
63
+ @request_task.wait
64
+
65
+ @response_task.stop
66
+ @response_task.wait
67
+
68
+ begin
69
+ @connection.close
70
+ rescue Errno::EPIPE
71
+ nil
72
+ end
73
+
74
+ reset!
75
+ end
76
+
77
+ private
78
+
79
+ def reset!
80
+ @connected = false
81
+ @connection = nil
82
+ @response_task = nil
83
+ @request_task = nil
84
+ end
85
+ end
86
+ 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,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module U
5
+ class << self
6
+ %w[addV V has count out values unfold].each do |step|
7
+ define_method step do |*args|
8
+ AnonymousStep.new(step, *args)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.1.3"
4
+ VERSION = "0.5.0"
5
5
  end
@@ -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
- "<V #{@label}(#{@id})>"
17
+ "v[#{@id}]"
18
18
  end
19
19
  alias to_s inspect
20
20
  end