grumlin 0.15.3 → 0.16.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.
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