grumlin 0.15.3 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -0
  3. data/CHANGELOG.md +10 -0
  4. data/Gemfile.lock +2 -2
  5. data/README.md +4 -0
  6. data/Rakefile +21 -3
  7. data/lib/definitions.yml +114 -0
  8. data/lib/grumlin/action.rb +124 -0
  9. data/lib/grumlin/client.rb +2 -2
  10. data/lib/grumlin/expressions/operator.rb +1 -1
  11. data/lib/grumlin/expressions/order.rb +1 -1
  12. data/lib/grumlin/expressions/p.rb +12 -17
  13. data/lib/grumlin/expressions/pop.rb +1 -1
  14. data/lib/grumlin/expressions/scope.rb +1 -1
  15. data/lib/grumlin/expressions/t.rb +1 -1
  16. data/lib/grumlin/expressions/text_p.rb +15 -0
  17. data/lib/grumlin/expressions/with_options.rb +17 -14
  18. data/lib/grumlin/repository.rb +2 -2
  19. data/lib/grumlin/shortcut.rb +27 -0
  20. data/lib/grumlin/shortcuts/properties.rb +6 -2
  21. data/lib/grumlin/shortcuts.rb +15 -13
  22. data/lib/grumlin/shortcuts_applyer.rb +48 -0
  23. data/lib/grumlin/step_data.rb +18 -0
  24. data/lib/grumlin/steps.rb +77 -0
  25. data/lib/grumlin/steps_serializers/bytecode.rb +65 -0
  26. data/lib/grumlin/steps_serializers/human_readable_bytecode.rb +36 -0
  27. data/lib/grumlin/steps_serializers/serializer.rb +16 -0
  28. data/lib/grumlin/steps_serializers/string.rb +42 -0
  29. data/lib/grumlin/sugar.rb +4 -4
  30. data/lib/grumlin/traversal_start.rb +51 -0
  31. data/lib/grumlin/typed_value.rb +0 -15
  32. data/lib/grumlin/version.rb +1 -1
  33. data/lib/grumlin.rb +6 -4
  34. metadata +14 -8
  35. data/lib/grumlin/anonymous_step.rb +0 -49
  36. data/lib/grumlin/bytecode.rb +0 -70
  37. data/lib/grumlin/expressions/u.rb +0 -19
  38. data/lib/grumlin/shortcut_proxy.rb +0 -53
  39. data/lib/grumlin/step.rb +0 -43
  40. data/lib/grumlin/traversal.rb +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b44c62fb657fac1ce08d4a4dcb05456a2750284dddcd5adbbd20f56bb2b9ef9c
4
- data.tar.gz: eccc2648799e68be2225ce606a06411b49953ce586e4b8249156dc680fc3522e
3
+ metadata.gz: 4a18e5e39768c0142eb219751991d67cb8512dd6d8cbeaa42494be193d44b694
4
+ data.tar.gz: 3c6833ffce68c409d78a656a892a81801b7820b9ea793dc60600e69c96315f76
5
5
  SHA512:
6
- metadata.gz: dfc9f36a53d49749425df0014005e18ddd47b91e5c35bdc4dd4c7dfc0ec3309ba15df4e8a3367a8dba4c92494a2f70cab9fe4a846ded833100dc09937bf4ff9b
7
- data.tar.gz: d315dfb2945c27091f76a21af2e30f77eb8753042519cf2bd7813ef544cb805f4970808158a550e5abdc1891c1526d4c3441bda46334bae67e2bf49a8ff9bd87
6
+ metadata.gz: 2c48059b03c81f68ff26802268f150c8d8af5476659e8f730de3c64c90f45896cdfa08dfd3bcc2db85240c34ef601b26317cbde269f3f834d6e3dd9f27a99f3e
7
+ data.tar.gz: 8033907ed22e1aa349b39ce3f247977242221d28aeea2f34c721e2bc6ec807f0a08dd21e450ef183fb989dac04534dc4fedbc1358e805ce5c8c57b59ab18b968
data/.rubocop.yml CHANGED
@@ -60,6 +60,9 @@ RSpec/MultipleExpectations:
60
60
  RSpec/DescribeClass:
61
61
  Enabled: false
62
62
 
63
+ RSpec/MultipleMemoizedHelpers:
64
+ Max: 7
65
+
63
66
  Style/WordArray:
64
67
  Exclude:
65
68
  - spec/**/*_spec.rb
@@ -77,3 +80,10 @@ Style/Documentation:
77
80
 
78
81
  Style/MultilineBlockChain:
79
82
  Enabled: false
83
+
84
+
85
+ # TODO:
86
+ # Style/SymbolArray:
87
+ # EnforcedStyle: brackets
88
+ # Style/WordArray:
89
+ # EnforcedStyle: brackets
data/CHANGELOG.md CHANGED
@@ -1,3 +1,13 @@
1
+ ## [0.16.0] - 2022-03-11
2
+
3
+ - Query building is rewritten from scratch. No public APIs were changed. [Details](https://github.com/babbel/grumlin/pull/64)
4
+ - Add support for [TextP](https://tinkerpop.apache.org/javadocs/current/core/org/apache/tinkerpop/gremlin/process/traversal/TextP.html)
5
+
6
+ ## [0.15.4] - 2022-01-20
7
+
8
+ - Move step and expression definitions to a yaml file for better diffs
9
+ - Add `definitions:format` rake task
10
+
1
11
  ## [0.15.3] - 2022-01-18
2
12
 
3
13
  - Fix passing nils as step arguments. Even if they are not supported by the server, they should not be omitted.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grumlin (0.15.3)
4
+ grumlin (0.16.0)
5
5
  async-pool (~> 0.3)
6
6
  async-websocket (~> 0.19)
7
7
  oj (~> 3.12)
@@ -28,7 +28,7 @@ GEM
28
28
  protocol-http (~> 0.22.0)
29
29
  protocol-http1 (~> 0.14.0)
30
30
  protocol-http2 (~> 0.14.0)
31
- async-io (1.32.2)
31
+ async-io (1.33.0)
32
32
  async
33
33
  async-pool (0.3.9)
34
34
  async (>= 1.25)
data/README.md CHANGED
@@ -258,6 +258,10 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
258
258
  the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
259
259
  push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
260
260
 
261
+ ### Adding new steps and expressions
262
+ To add a new step or an expression simple put it to the corresponding list in [definitions.yml](lib/definitions.yml)
263
+ and run `rake definitions:format`. You don't need to properly sort the lists manually, the rake task will do it for you.
264
+
261
265
  ## Contributing
262
266
 
263
267
  Bug reports and pull requests are welcome on GitHub at https://github.com/zhulik/grumlin. This project is intended to
data/Rakefile CHANGED
@@ -1,12 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "yaml"
4
+
3
5
  require "bundler/gem_tasks"
4
6
  require "rspec/core/rake_task"
7
+ require "rubocop/rake_task"
5
8
 
6
9
  RSpec::Core::RakeTask.new(:spec)
10
+ RuboCop::RakeTask.new
7
11
 
8
- require "rubocop/rake_task"
12
+ task default: %i[rubocop spec]
9
13
 
10
- RuboCop::RakeTask.new
14
+ namespace :definitions do
15
+ desc "Format definitions.yml"
16
+ task :format do
17
+ path = File.join(__dir__, "lib", "definitions.yml")
18
+ definitions = YAML.safe_load(File.read(path))
19
+
20
+ definitions.each_value do |kind|
21
+ kind.each do |name, list|
22
+ next if name == "with_options"
23
+
24
+ list.sort!
25
+ end
26
+ end
11
27
 
12
- task default: %i[spec rubocop]
28
+ File.write(path, YAML.dump(definitions))
29
+ end
30
+ end
@@ -0,0 +1,114 @@
1
+ ---
2
+ steps:
3
+ regular:
4
+ - E
5
+ - V
6
+ - addE
7
+ - addV
8
+ - aggregate
9
+ - and
10
+ - as
11
+ - both
12
+ - bothE
13
+ - by
14
+ - choose
15
+ - coalesce
16
+ - constant
17
+ - count
18
+ - dedup
19
+ - drop
20
+ - elementMap
21
+ - emit
22
+ - fold
23
+ - from
24
+ - group
25
+ - groupCount
26
+ - has
27
+ - hasId
28
+ - hasLabel
29
+ - hasNot
30
+ - id
31
+ - identity
32
+ - in
33
+ - inE
34
+ - inV
35
+ - inject
36
+ - is
37
+ - label
38
+ - limit
39
+ - map
40
+ - none
41
+ - not
42
+ - option
43
+ - or
44
+ - order
45
+ - out
46
+ - outE
47
+ - outV
48
+ - path
49
+ - project
50
+ - properties
51
+ - property
52
+ - range
53
+ - repeat
54
+ - sack
55
+ - select
56
+ - sideEffect
57
+ - skip
58
+ - sum
59
+ - tail
60
+ - timeLimit
61
+ - to
62
+ - unfold
63
+ - union
64
+ - until
65
+ - valueMap
66
+ - values
67
+ - where
68
+ - with
69
+ start:
70
+ - E
71
+ - V
72
+ - addE
73
+ - addV
74
+ - inject
75
+ configuration:
76
+ - withSack
77
+ - withSideEffect
78
+ expressions:
79
+ operator:
80
+ - addAll
81
+ - and
82
+ - assign
83
+ - div
84
+ - max
85
+ - min
86
+ - minus
87
+ - mult
88
+ - or
89
+ - sum
90
+ order:
91
+ - asc
92
+ - desc
93
+ - shuffle
94
+ pop:
95
+ - all
96
+ - first
97
+ - last
98
+ - mixed
99
+ scope:
100
+ - local
101
+ t:
102
+ - id
103
+ - label
104
+ with_options:
105
+ tokens: "~tinkerpop.valueMap.tokens"
106
+ none: 0
107
+ ids: 1
108
+ labels: 2
109
+ keys: 4
110
+ values: 8
111
+ all: 15
112
+ indexer: "~tinkerpop.index.indexer"
113
+ list: 0
114
+ map: 1
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Action
5
+ START_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
6
+ CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
7
+ REGULAR_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze
8
+
9
+ ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS
10
+
11
+ attr_reader :name, :args, :params, :shortcuts, :next_step, :configuration_steps, :previous_step
12
+
13
+ def initialize(name, args: [], params: {}, previous_step: nil, shortcuts: {}, pool: Grumlin.default_pool)
14
+ @name = name.to_sym
15
+ @args = args # TODO: add recursive validation: only json types or Action
16
+ @params = params # TODO: add recursive validation: only json types
17
+ @previous_step = previous_step
18
+ @shortcuts = shortcuts
19
+ @pool = pool
20
+ end
21
+
22
+ ALL_STEPS.each do |step|
23
+ define_method step do |*args, **params|
24
+ step(step, *args, **params)
25
+ end
26
+ end
27
+
28
+ def step(name, *args, **params)
29
+ Action.new(name, args: args, params: params, previous_step: self, shortcuts: @shortcuts, pool: @pool)
30
+ end
31
+
32
+ def configuration_step?
33
+ CONFIGURATION_STEPS.include?(@name)
34
+ end
35
+
36
+ def start_step?
37
+ START_STEPS.include?(@name)
38
+ end
39
+
40
+ def regular_step?
41
+ REGULAR_STEPS.include?(@name)
42
+ end
43
+
44
+ def supported_step?
45
+ ALL_STEPS.include?(@name)
46
+ end
47
+
48
+ def shortcut?
49
+ @shortcuts.key?(@name)
50
+ end
51
+
52
+ def arguments
53
+ @arguments ||= [*@args].tap do |args|
54
+ args << @params if @params.any?
55
+ end
56
+ end
57
+
58
+ def method_missing(name, *args, **params)
59
+ return step(name, *args, **params) if @shortcuts.key?(name)
60
+
61
+ super
62
+ end
63
+
64
+ def ==(other)
65
+ self.class == other.class &&
66
+ @name == other.name &&
67
+ @args == other.args &&
68
+ @params == other.params &&
69
+ @previous_step == other.previous_step &&
70
+ @shortcuts == other.shortcuts
71
+ end
72
+
73
+ def steps
74
+ @steps ||= Steps.from(self)
75
+ end
76
+
77
+ def to_s(**params)
78
+ StepsSerializers::String.new(steps, **params).serialize
79
+ end
80
+
81
+ # TODO: add human readable mode
82
+ def inspect
83
+ conf_steps, regular_steps = StepsSerializers::HumanReadableBytecode.new(steps).serialize
84
+ "#{conf_steps.any? ? conf_steps : nil}#{regular_steps}"
85
+ end
86
+
87
+ def bytecode(no_return: false)
88
+ StepsSerializers::Bytecode.new(steps, no_return: no_return)
89
+ end
90
+
91
+ def next
92
+ to_enum.next
93
+ end
94
+
95
+ def hasNext # rubocop:disable Naming/MethodName
96
+ to_enum.peek
97
+ true
98
+ rescue StopIteration
99
+ false
100
+ end
101
+
102
+ def to_enum
103
+ @to_enum ||= toList.to_enum
104
+ end
105
+
106
+ def toList
107
+ @pool.acquire do |client|
108
+ client.write(bytecode)
109
+ end
110
+ end
111
+
112
+ def iterate
113
+ @pool.acquire do |client|
114
+ client.write(bytecode(no_return: true))
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def respond_to_missing?(name, _include_private = false)
121
+ @shortcuts.key?(name)
122
+ end
123
+ end
124
+ end
@@ -97,7 +97,7 @@ module Grumlin
97
97
  def write(bytecode)
98
98
  raise NotConnectedError unless connected?
99
99
 
100
- request = to_query(bytecode.to_bytecode)
100
+ request = to_query(bytecode)
101
101
  channel = @request_dispatcher.add_request(request)
102
102
  @transport.write(request)
103
103
 
@@ -130,7 +130,7 @@ module Grumlin
130
130
  op: "bytecode",
131
131
  processor: "traversal",
132
132
  args: {
133
- gremlin: bytecode,
133
+ gremlin: { :@type => "g:Bytecode", :@value => bytecode.serialize },
134
134
  aliases: { g: :g }
135
135
  }
136
136
  }
@@ -3,7 +3,7 @@
3
3
  module Grumlin
4
4
  module Expressions
5
5
  module Operator
6
- SUPPORTED_STEPS = %i[addAll and assign div max min minus mult or sum].freeze
6
+ SUPPORTED_STEPS = Grumlin.definitions.dig(:expressions, :operator).map(&:to_sym).freeze
7
7
 
8
8
  class << self
9
9
  extend Expression
@@ -3,7 +3,7 @@
3
3
  module Grumlin
4
4
  module Expressions
5
5
  module Order
6
- SUPPORTED_STEPS = %i[asc desc shuffle].freeze
6
+ SUPPORTED_STEPS = Grumlin.definitions.dig(:expressions, :order).map(&:to_sym).freeze
7
7
 
8
8
  class << self
9
9
  extend Expression
@@ -2,28 +2,23 @@
2
2
 
3
3
  module Grumlin
4
4
  module Expressions
5
- module P
6
- class << self
7
- class Predicate < TypedValue
8
- def initialize(name, args:, arg_type: nil)
9
- super(type: "P")
10
- @name = name
11
- @args = args
12
- @arg_type = arg_type
13
- end
5
+ class P
6
+ class Predicate
7
+ attr_reader :namespace, :name, :value, :type
14
8
 
15
- def value
16
- @value ||= {
17
- predicate: @name,
18
- value: TypedValue.new(type: @arg_type, value: @args).to_bytecode
19
- }
20
- end
9
+ def initialize(namespace, name, value:, type: nil)
10
+ @namespace = namespace
11
+ @name = name
12
+ @value = value
13
+ @type = type
21
14
  end
15
+ end
22
16
 
17
+ class << self
23
18
  # TODO: support more predicates
24
19
  %i[eq neq].each do |predicate|
25
20
  define_method predicate do |*args|
26
- Predicate.new(predicate, args: args[0])
21
+ Predicate.new("P", predicate, value: args[0])
27
22
  end
28
23
  end
29
24
 
@@ -36,7 +31,7 @@ module Grumlin
36
31
  else
37
32
  args.to_a
38
33
  end
39
- Predicate.new(predicate, args: args, arg_type: "List")
34
+ Predicate.new("P", predicate, value: args, type: "List")
40
35
  end
41
36
  end
42
37
  end
@@ -3,7 +3,7 @@
3
3
  module Grumlin
4
4
  module Expressions
5
5
  module Pop
6
- SUPPORTED_STEPS = %i[all first last mixed].freeze
6
+ SUPPORTED_STEPS = Grumlin.definitions.dig(:expressions, :pop).map(&:to_sym).freeze
7
7
 
8
8
  class << self
9
9
  extend Expression
@@ -3,7 +3,7 @@
3
3
  module Grumlin
4
4
  module Expressions
5
5
  module Scope
6
- SUPPORTED_STEPS = %i[local].freeze
6
+ SUPPORTED_STEPS = Grumlin.definitions.dig(:expressions, :scope).map(&:to_sym).freeze
7
7
 
8
8
  class << self
9
9
  extend Expression
@@ -3,7 +3,7 @@
3
3
  module Grumlin
4
4
  module Expressions
5
5
  module T
6
- SUPPORTED_STEPS = %i[id label].freeze
6
+ SUPPORTED_STEPS = Grumlin.definitions.dig(:expressions, :t).map(&:to_sym).freeze
7
7
 
8
8
  class << self
9
9
  extend Expression
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module Expressions
5
+ class TextP < P
6
+ class << self
7
+ %i[containing endingWith notContaining notEndingWith notStartingWith startingWith].each do |predicate|
8
+ define_method predicate do |*args|
9
+ P::Predicate.new("TextP", predicate, value: args[0])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,27 +2,30 @@
2
2
 
3
3
  module Grumlin
4
4
  module Expressions
5
- module WithOptions
6
- WITH_OPTIONS = {
7
- tokens: "~tinkerpop.valueMap.tokens",
8
- none: 0,
9
- ids: 1,
10
- labels: 2,
11
- keys: 4,
12
- values: 8,
13
- all: 15,
14
- indexer: "~tinkerpop.index.indexer",
15
- list: 0,
16
- map: 1
17
- }.freeze
5
+ class WithOptions
6
+ WITH_OPTIONS = Grumlin.definitions.dig(:expressions, :with_options).freeze
18
7
 
19
8
  class << self
20
9
  WITH_OPTIONS.each do |k, v|
21
10
  define_method k do
22
- v
11
+ name = "@#{k}"
12
+ return instance_variable_get(name) if instance_variable_defined?(name)
13
+
14
+ instance_variable_set(name, WithOptions.new(k, v))
23
15
  end
24
16
  end
25
17
  end
18
+
19
+ attr_reader :name, :value
20
+
21
+ def initialize(name, value)
22
+ @name = name
23
+ @value = value
24
+ end
25
+
26
+ def to_s
27
+ "WithOptions.#{@name}"
28
+ end
26
29
  end
27
30
  end
28
31
  end
@@ -4,11 +4,11 @@ module Grumlin
4
4
  module Repository
5
5
  module InstanceMethods
6
6
  def __
7
- with_shortcuts(Grumlin::Expressions::U)
7
+ TraversalStart.new(self.class.shortcuts)
8
8
  end
9
9
 
10
10
  def g
11
- with_shortcuts(Grumlin::Traversal.new)
11
+ TraversalStart.new(self.class.shortcuts)
12
12
  end
13
13
  end
14
14
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Shortcut
5
+ extend Forwardable
6
+
7
+ attr_reader :name, :block
8
+
9
+ def_delegator :@block, :arity
10
+ def_delegator :@block, :source_location
11
+
12
+ def initialize(name, &block)
13
+ @name = name
14
+ @block = block
15
+ end
16
+
17
+ def ==(other)
18
+ @name == other.name && @block == other.block
19
+ end
20
+
21
+ # TODO: to_s, inspect, preview
22
+
23
+ def apply(object, *args, **params)
24
+ object.instance_exec(*args, **params, &@block)
25
+ end
26
+ end
27
+ end
@@ -5,13 +5,17 @@ module Grumlin
5
5
  module Properties
6
6
  extend Grumlin::Shortcuts
7
7
 
8
- shortcut :props do |*_args, **props|
8
+ shortcut :props do |props|
9
+ next if props.nil? # TODO: fixme, add proper support for **params
10
+
9
11
  props.reduce(self) do |tt, (prop, value)|
10
12
  tt.property(prop, value)
11
13
  end
12
14
  end
13
15
 
14
- shortcut :hasAll do |*, **props|
16
+ shortcut :hasAll do |props|
17
+ next if props.nil? # TODO: fixme, add proper support for **params
18
+
15
19
  props.reduce(self) do |tt, (prop, value)|
16
20
  tt.has(prop, value)
17
21
  end
@@ -2,14 +2,7 @@
2
2
 
3
3
  module Grumlin
4
4
  module Shortcuts
5
- module InstanceMethods
6
- def with_shortcuts(obj)
7
- ShortcutProxy.new(obj, self.class.shortcuts, parent: self)
8
- end
9
- end
10
-
11
5
  def self.extended(base)
12
- base.include(InstanceMethods)
13
6
  base.include(Grumlin::Expressions)
14
7
  end
15
8
 
@@ -18,19 +11,28 @@ module Grumlin
18
11
  subclass.shortcuts_from(self)
19
12
  end
20
13
 
21
- def shortcut(name, &block)
14
+ def shortcut(name, shortcut = nil, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
22
15
  name = name.to_sym
23
16
  # TODO: blocklist of names to avoid conflicts with standard methods?
24
- raise ArgumentError, "cannot use names of standard gremlin steps" if Grumlin.supported_steps.include?(name)
17
+ if Grumlin::Action::REGULAR_STEPS.include?(name)
18
+ raise ArgumentError,
19
+ "cannot use names of standard gremlin steps"
20
+ end
21
+
22
+ if (shortcut.nil? && block.nil?) || (shortcut && block)
23
+ raise ArgumentError, "either shortcut or block must be passed"
24
+ end
25
+
26
+ shortcut ||= Shortcut.new(name, &block)
25
27
 
26
- raise ArgumentError, "shortcut '#{name}' already exists" if shortcuts.key?(name) && shortcuts[name] != block
28
+ raise ArgumentError, "shortcut '#{name}' already exists" if shortcuts.key?(name) && shortcuts[name] != shortcut
27
29
 
28
- shortcuts[name] = block
30
+ shortcuts[name] = shortcut
29
31
  end
30
32
 
31
33
  def shortcuts_from(other_shortcuts)
32
- other_shortcuts.shortcuts.each do |name, block|
33
- shortcut(name, &block)
34
+ other_shortcuts.shortcuts.each do |name, shortcut|
35
+ shortcut(name, shortcut)
34
36
  end
35
37
  end
36
38