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
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class ShortcutsApplyer
5
+ class << self
6
+ def call(steps)
7
+ new.call(steps)
8
+ end
9
+ end
10
+
11
+ def call(steps)
12
+ return steps unless steps.uses_shortcuts?
13
+
14
+ shortcuts = steps.shortcuts
15
+
16
+ configuration_steps = process_steps(steps.configuration_steps, shortcuts)
17
+ regular_steps = process_steps(steps.steps, shortcuts)
18
+
19
+ Steps.new(shortcuts).tap do |processed_steps|
20
+ (configuration_steps + regular_steps).each do |step|
21
+ processed_steps.add(step.name, step.arguments)
22
+ end
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def process_steps(steps, shortcuts) # rubocop:disable Metrics/AbcSize
29
+ steps.each_with_object([]) do |step, result|
30
+ arguments = step.arguments.map do |arg|
31
+ arg.is_a?(Steps) ? ShortcutsApplyer.call(arg) : arg
32
+ end
33
+
34
+ if shortcuts.include?(step.name)
35
+ t = TraversalStart.new(shortcuts)
36
+ action = shortcuts[step.name].apply(t, *arguments)
37
+ next if action.nil? || action == t # Shortcut did not add any steps
38
+
39
+ new_steps = ShortcutsApplyer.call(Steps.from(action))
40
+ result.concat(new_steps.configuration_steps)
41
+ result.concat(new_steps.steps)
42
+ else
43
+ result << StepData.new(step.name, arguments)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class StepData
5
+ attr_reader :name, :arguments
6
+
7
+ def initialize(name, arguments)
8
+ @name = name
9
+ @arguments = arguments
10
+ end
11
+
12
+ def ==(other)
13
+ self.class == other.class &&
14
+ @name == other.name &&
15
+ @arguments == other.arguments
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class Steps
5
+ CONFIGURATION_STEPS = Action::CONFIGURATION_STEPS
6
+ ALL_STEPS = Action::ALL_STEPS
7
+
8
+ def self.from(action)
9
+ raise ArgumentError, "expected: #{Action}, given: #{action.class}" unless action.is_a?(Action)
10
+
11
+ shortcuts = action.shortcuts
12
+ actions = []
13
+
14
+ until action.nil?
15
+ actions.unshift(action)
16
+ action = action.previous_step
17
+ end
18
+
19
+ new(shortcuts).tap do |chain|
20
+ actions.each do |act|
21
+ chain.add(act.name, act.arguments)
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_reader :configuration_steps, :steps, :shortcuts
27
+
28
+ def initialize(shortcuts, configuration_steps: [], steps: [])
29
+ @shortcuts = shortcuts
30
+ @configuration_steps = configuration_steps
31
+ @steps = steps
32
+ end
33
+
34
+ def add(name, arguments)
35
+ return add_configuration_step(name, arguments) if CONFIGURATION_STEPS.include?(name)
36
+
37
+ StepData.new(name, cast_arguments(arguments)).tap do |step|
38
+ @steps << step
39
+ end
40
+ end
41
+
42
+ def uses_shortcuts?
43
+ shortcuts?(@configuration_steps) || shortcuts?(@steps)
44
+ end
45
+
46
+ def ==(other)
47
+ self.class == other.class &&
48
+ @shortcuts == other.shortcuts &&
49
+ @configuration_steps == other.configuration_steps &&
50
+ @steps == other.steps
51
+ end
52
+
53
+ # TODO: add #bytecode, to_s, inspect
54
+
55
+ private
56
+
57
+ def shortcuts?(steps_ary)
58
+ steps_ary.any? do |step|
59
+ @shortcuts.include?(step.name) || step.arguments.any? do |arg|
60
+ arg.is_a?(Steps) ? arg.uses_shortcuts? : false
61
+ end
62
+ end
63
+ end
64
+
65
+ def add_configuration_step(name, arguments)
66
+ raise ArgumentError, "cannot use configuration steps after start step was used" unless @steps.empty?
67
+
68
+ StepData.new(name, cast_arguments(arguments)).tap do |step|
69
+ @configuration_steps << step
70
+ end
71
+ end
72
+
73
+ def cast_arguments(arguments)
74
+ arguments.map { |arg| arg.is_a?(Action) ? Steps.from(arg) : arg }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class Bytecode < Serializer
6
+ # constructor params: no_return: true|false, default false
7
+ # TODO: add pretty
8
+
9
+ NONE_STEP = StepData.new("none", [])
10
+
11
+ def serialize
12
+ steps = ShortcutsApplyer.call(@steps)
13
+ no_return = @params[:no_return] || false
14
+
15
+ {
16
+ step: (steps.steps + (no_return ? [NONE_STEP] : [])).map { |s| serialize_step(s) }
17
+ }.tap do |v|
18
+ v.merge!(source: steps.configuration_steps.map { |s| serialize_step(s) }) if steps.configuration_steps.any?
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def serialize_step(step)
25
+ [step.name, *step.arguments.map { |arg| serialize_arg(arg) }]
26
+ end
27
+
28
+ def serialize_arg(arg)
29
+ return serialize_typed_value(arg) if arg.is_a?(TypedValue)
30
+ return serialize_predicate(arg) if arg.is_a?(Expressions::P::Predicate)
31
+ return arg.value if arg.is_a?(Expressions::WithOptions)
32
+
33
+ return arg unless arg.is_a?(Steps)
34
+
35
+ { :@type => "g:Bytecode", :@value => Bytecode.new(arg, **@params.merge(no_return: false)).serialize }
36
+ end
37
+
38
+ def serialize_typed_value(value)
39
+ return value.value if value.type.nil?
40
+
41
+ {
42
+ "@type": "g:#{value.type}",
43
+ "@value": value.value
44
+ }
45
+ end
46
+
47
+ def serialize_predicate(value)
48
+ {
49
+ "@type": "g:#{value.namespace}",
50
+ "@value": {
51
+ predicate: value.name,
52
+ value: if value.type.nil?
53
+ value.value
54
+ else
55
+ {
56
+ "@type": "g:#{value.type}",
57
+ "@value": value.value
58
+ }
59
+ end
60
+ }
61
+ }
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class HumanReadableBytecode < Serializer
6
+ def serialize
7
+ steps = ShortcutsApplyer.call(@steps)
8
+ [serialize_steps(steps.configuration_steps), serialize_steps(steps.steps)]
9
+ end
10
+
11
+ def serialize_steps(steps)
12
+ steps.map { |s| serialize_step(s) }
13
+ end
14
+
15
+ private
16
+
17
+ def serialize_step(step)
18
+ [step.name, *step.arguments.map { |arg| serialize_arg(arg) }]
19
+ end
20
+
21
+ def serialize_arg(arg)
22
+ return arg.to_s if arg.is_a?(TypedValue)
23
+ return serialize_predicate(arg) if arg.is_a?(Expressions::P::Predicate)
24
+ return arg.value if arg.is_a?(Expressions::WithOptions)
25
+
26
+ return arg unless arg.is_a?(Steps)
27
+
28
+ HumanReadableBytecode.new(arg, **@params.merge(no_return: false)).serialize[1]
29
+ end
30
+
31
+ def serialize_predicate(arg)
32
+ "#{arg.name}(#{arg.value})"
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class Serializer
6
+ def initialize(steps, **params)
7
+ @steps = steps
8
+ @params = params
9
+ end
10
+
11
+ def serialize
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ module StepsSerializers
5
+ class String < Serializer
6
+ # constructor params: apply_shortcuts: true|false, default: false
7
+ # constructor params: anonymous: true|false, default: false
8
+ # TODO: add pretty
9
+
10
+ def serialize
11
+ steps = @params[:apply_shortcuts] ? ShortcutsApplyer.call(@steps) : @steps
12
+
13
+ configuration_steps = serialize_steps(steps.configuration_steps)
14
+ regular_steps = serialize_steps(steps.steps)
15
+
16
+ "#{prefix}.#{(configuration_steps + regular_steps).join(".")}"
17
+ end
18
+
19
+ private
20
+
21
+ def prefix
22
+ @prefix ||= @params[:anonymous] ? "__" : "g"
23
+ end
24
+
25
+ def serialize_arg(arg)
26
+ return "\"#{arg}\"" if arg.is_a?(::String) || arg.is_a?(Symbol)
27
+ return "#{arg.type}.#{arg.value}" if arg.is_a?(Grumlin::TypedValue)
28
+ return arg.to_s if arg.is_a?(Grumlin::Expressions::WithOptions)
29
+
30
+ return arg unless arg.is_a?(Steps)
31
+
32
+ StepsSerializers::String.new(arg, anonymous: true, **@params).serialize
33
+ end
34
+
35
+ def serialize_steps(steps)
36
+ steps.map do |step|
37
+ "#{step.name}(#{step.arguments.map { |a| serialize_arg(a) }.join(", ")})"
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
data/lib/grumlin/sugar.rb CHANGED
@@ -6,12 +6,12 @@ module Grumlin
6
6
  base.include(Grumlin::Expressions)
7
7
  end
8
8
 
9
- def __
10
- Grumlin::Expressions::U
9
+ def __(shortcuts = {})
10
+ Grumlin::TraversalStart.new(shortcuts) # TODO: allow only regular and start steps
11
11
  end
12
12
 
13
- def g
14
- Grumlin::Traversal.new
13
+ def g(shortcuts = {})
14
+ Grumlin::TraversalStart.new(shortcuts)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Grumlin
4
+ class TraversalStart
5
+ START_STEPS = Grumlin.definitions.dig(:steps, :start).map(&:to_sym).freeze
6
+ REGULAR_STEPS = Grumlin.definitions.dig(:steps, :regular).map(&:to_sym).freeze
7
+ CONFIGURATION_STEPS = Grumlin.definitions.dig(:steps, :configuration).map(&:to_sym).freeze
8
+
9
+ ALL_STEPS = START_STEPS + CONFIGURATION_STEPS + REGULAR_STEPS
10
+
11
+ ALL_STEPS.each do |step|
12
+ define_method step do |*args, **params|
13
+ step(step, *args, **params)
14
+ end
15
+ end
16
+
17
+ attr_reader :shortcuts
18
+
19
+ def initialize(shortcuts)
20
+ @shortcuts = shortcuts
21
+ end
22
+
23
+ def step(name, *args, **params)
24
+ Action.new(name, args: args, params: params, shortcuts: @shortcuts)
25
+ end
26
+
27
+ def method_missing(name, *args, **params)
28
+ return step(name, *args, **params) if @shortcuts.key?(name)
29
+
30
+ super
31
+ end
32
+
33
+ def __
34
+ TraversalStart.new(@shortcuts) # TODO: allow only regular and start steps
35
+ end
36
+
37
+ def to_s(*)
38
+ self.class.to_s
39
+ end
40
+
41
+ def inspect
42
+ self.class.inspect
43
+ end
44
+
45
+ private
46
+
47
+ def respond_to_missing?(name, _include_private = false)
48
+ @shortcuts.key?(name)
49
+ end
50
+ end
51
+ end
@@ -9,17 +9,6 @@ module Grumlin
9
9
  @value = value
10
10
  end
11
11
 
12
- def to_bytecode
13
- @to_bytecode ||= if type.nil?
14
- value
15
- else
16
- {
17
- "@type": "g:#{type}",
18
- "@value": value
19
- }
20
- end
21
- end
22
-
23
12
  def inspect
24
13
  "<#{type}.#{value}>"
25
14
  end
@@ -27,9 +16,5 @@ module Grumlin
27
16
  def to_s
28
17
  inspect
29
18
  end
30
-
31
- def to_readable_bytecode
32
- inspect
33
- end
34
19
  end
35
20
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Grumlin
4
- VERSION = "0.15.3"
4
+ VERSION = "0.16.0"
5
5
  end
data/lib/grumlin.rb CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  require "securerandom"
4
4
  require "oj"
5
+ require "yaml"
5
6
 
7
+ # TODO: use Oj directly
6
8
  Oj.mimic_JSON
7
9
  Oj.add_to_json
8
10
 
@@ -108,10 +110,6 @@ module Grumlin
108
110
  end
109
111
  end
110
112
 
111
- def self.supported_steps
112
- @supported_steps ||= (Grumlin::AnonymousStep::SUPPORTED_STEPS + Grumlin::Expressions::U::SUPPORTED_STEPS).sort.uniq
113
- end
114
-
115
113
  @pool_mutex = Mutex.new
116
114
 
117
115
  class << self
@@ -145,6 +143,10 @@ module Grumlin
145
143
  Thread.current.thread_variable_set(:grumlin_default_pool, nil)
146
144
  end
147
145
  end
146
+
147
+ def definitions
148
+ @definitions ||= YAML.safe_load(File.read(File.join(__dir__, "definitions.yml")), symbolize_names: true)
149
+ end
148
150
  end
149
151
  end
150
152
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grumlin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.3
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-18 00:00:00.000000000 Z
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-pool
@@ -96,9 +96,9 @@ files:
96
96
  - gremlin_server/tinkergraph-empty.properties
97
97
  - grumlin.gemspec
98
98
  - lib/async/channel.rb
99
+ - lib/definitions.yml
99
100
  - lib/grumlin.rb
100
- - lib/grumlin/anonymous_step.rb
101
- - lib/grumlin/bytecode.rb
101
+ - lib/grumlin/action.rb
102
102
  - lib/grumlin/client.rb
103
103
  - lib/grumlin/edge.rb
104
104
  - lib/grumlin/expressions/expression.rb
@@ -108,22 +108,28 @@ files:
108
108
  - lib/grumlin/expressions/pop.rb
109
109
  - lib/grumlin/expressions/scope.rb
110
110
  - lib/grumlin/expressions/t.rb
111
- - lib/grumlin/expressions/u.rb
111
+ - lib/grumlin/expressions/text_p.rb
112
112
  - lib/grumlin/expressions/with_options.rb
113
113
  - lib/grumlin/path.rb
114
114
  - lib/grumlin/property.rb
115
115
  - lib/grumlin/repository.rb
116
116
  - lib/grumlin/request_dispatcher.rb
117
- - lib/grumlin/shortcut_proxy.rb
117
+ - lib/grumlin/shortcut.rb
118
118
  - lib/grumlin/shortcuts.rb
119
119
  - lib/grumlin/shortcuts/properties.rb
120
- - lib/grumlin/step.rb
120
+ - lib/grumlin/shortcuts_applyer.rb
121
+ - lib/grumlin/step_data.rb
122
+ - lib/grumlin/steps.rb
123
+ - lib/grumlin/steps_serializers/bytecode.rb
124
+ - lib/grumlin/steps_serializers/human_readable_bytecode.rb
125
+ - lib/grumlin/steps_serializers/serializer.rb
126
+ - lib/grumlin/steps_serializers/string.rb
121
127
  - lib/grumlin/sugar.rb
122
128
  - lib/grumlin/test/rspec.rb
123
129
  - lib/grumlin/test/rspec/db_cleaner_context.rb
124
130
  - lib/grumlin/test/rspec/gremlin_context.rb
125
131
  - lib/grumlin/transport.rb
126
- - lib/grumlin/traversal.rb
132
+ - lib/grumlin/traversal_start.rb
127
133
  - lib/grumlin/traverser.rb
128
134
  - lib/grumlin/typed_value.rb
129
135
  - lib/grumlin/typing.rb
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- class AnonymousStep
5
- attr_reader :name, :previous_step, :configuration_steps
6
-
7
- # TODO: add other steps
8
- SUPPORTED_STEPS = %i[E V addE addV aggregate and as both bothE by choose coalesce count dedup drop elementMap emit
9
- fold from group groupCount has hasId hasLabel hasNot id identity in inE inV is label limit
10
- map not or order out outE path project properties property range repeat sack select sideEffect
11
- skip sum tail to unfold union until valueMap values where with].freeze
12
-
13
- def initialize(name, *args, configuration_steps: [], previous_step: nil, **params)
14
- @name = name
15
- @previous_step = previous_step
16
- @args = args
17
- @params = params
18
- @configuration_steps = configuration_steps
19
- end
20
-
21
- SUPPORTED_STEPS.each do |step|
22
- define_method(step) do |*args, **params|
23
- step(step, *args, **params)
24
- end
25
- end
26
-
27
- def step(name, *args, **params)
28
- self.class.new(name, *args, previous_step: self, configuration_steps: configuration_steps, **params)
29
- end
30
-
31
- def inspect
32
- bytecode.inspect
33
- end
34
-
35
- def to_s
36
- inspect
37
- end
38
-
39
- def bytecode(no_return: false)
40
- @bytecode ||= Bytecode.new(self, no_return: no_return)
41
- end
42
-
43
- def args
44
- [*@args].tap do |args|
45
- args << @params if @params.any?
46
- end
47
- end
48
- end
49
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- # Incapsulates logic of converting step chains and step arguments to queries that can be sent to the server
5
- # and to human readable strings.
6
- class Bytecode < TypedValue
7
- class NoneStep
8
- def to_bytecode
9
- ["none"]
10
- end
11
- end
12
-
13
- NONE_STEP = NoneStep.new
14
-
15
- def initialize(step, no_return: false)
16
- super(type: "Bytecode")
17
- @step = step
18
- @no_return = no_return
19
- end
20
-
21
- def inspect
22
- configuration_steps = @step.configuration_steps.map do |s|
23
- serialize_arg(s, serialization_method: :to_readable_bytecode)
24
- end
25
- "#{configuration_steps.any? ? configuration_steps : nil}#{to_readable_bytecode}"
26
- end
27
-
28
- def to_s
29
- inspect
30
- end
31
-
32
- def to_readable_bytecode
33
- @to_readable_bytecode ||= steps.map { |s| serialize_arg(s, serialization_method: :to_readable_bytecode) }
34
- end
35
-
36
- def value
37
- @value ||= { step: (steps + (@no_return ? [NONE_STEP] : [])).map { |s| serialize_arg(s) } }.tap do |v|
38
- v.merge!(source: @step.configuration_steps.map { |s| serialize_arg(s) }) if @step.configuration_steps.any?
39
- end
40
- end
41
-
42
- private
43
-
44
- # Serializes step or a step argument to either an executable query or a human readable string representation
45
- # depending on the `serialization_method` parameter. It should be either `:to_readable_bytecode` for human readable
46
- # representation or `:to_bytecode` for query.
47
- def serialize_arg(arg, serialization_method: :to_bytecode)
48
- return arg.public_send(serialization_method) if arg.respond_to?(serialization_method)
49
- return arg unless arg.is_a?(AnonymousStep)
50
-
51
- arg.args.each.with_object([arg.name.to_s]) do |a, res|
52
- res << if a.respond_to?(:bytecode)
53
- a.bytecode.public_send(serialization_method)
54
- else
55
- serialize_arg(a, serialization_method: serialization_method)
56
- end
57
- end
58
- end
59
-
60
- def steps
61
- @steps ||= [].tap do |result|
62
- step = @step
63
- until step.nil?
64
- result.unshift(step)
65
- step = step.previous_step
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Grumlin
4
- module Expressions
5
- module U
6
- # TODO: add other start steps
7
- SUPPORTED_STEPS = %i[V addV coalesce constant count drop fold has hasLabel hasNot id identity in inE inV is label
8
- out outE outV project repeat select timeLimit unfold valueMap values].freeze
9
-
10
- class << self
11
- SUPPORTED_STEPS.each do |step|
12
- define_method step do |*args, **params|
13
- AnonymousStep.new(step, *args, **params)
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end