activegraph 11.3.1 → 11.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1ae9faa69f9792692e849f1d3de64c0a22b8e5623302cde0b1e707bfb443bba
4
- data.tar.gz: 57cb8e74aae28b60346bd7c908ed4e793df433c0722b3ab2322320d5a9bab217
3
+ metadata.gz: f068da2126bbed4301cf9fb15c34a708ef7f7a152afbfe42af5482237a041f0d
4
+ data.tar.gz: 0c87c4c2082a2e999ac8e0ff165e0cdbffe875f7f2eae407ea31f3f119d46a5f
5
5
  SHA512:
6
- metadata.gz: c5de6bb15067a8c93a14be2dd8949d70d0edcbc1bdabcab2c0880e11dda13f9bfcbdddc51c82438d7cd1642fafb841cbe6b03ca714d1633f3c8b09396531eb0c
7
- data.tar.gz: 976b9fa40c5056de62e7dc9fc3437c107adf13b09f5d1b07b660b79f92f74c9434c18114e4bab1c16e845f529e145c4b337e461cf77b5a4f40d5255a9619103f
6
+ metadata.gz: 5c12b0fc53c6bb04d661ec6c1669843e3c59d2ce00a9c7ea909d49b87ba7ef7b4c58ba2196d2c880c70da4ed727d14851fa27a15cf948125e91ee7670a23ff9d
7
+ data.tar.gz: e0dda99c677170338a7a470d00660fa20528e272452ee81a58722d055af969f0951e3344e0961e3b5ec49b6e0e316527dc0deab5f0ce01fe3dda0c029310ee01
data/CHANGELOG.md CHANGED
@@ -3,6 +3,12 @@ All notable changes to this project will be documented in this file.
3
3
  This file should follow the standards specified on [http://keepachangelog.com/]
4
4
  This project adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
+ ## [11.4.0] 2023-02-12
7
+
8
+ ## Fixed
9
+
10
+ - Added support for cypher UNION
11
+
6
12
  ## [11.3.1] 2023-02-12
7
13
 
8
14
  ## Fixed
@@ -162,8 +162,8 @@ module ActiveGraph
162
162
  # DETACH DELETE clause
163
163
  # @return [Query]
164
164
 
165
- METHODS = %w[start match optional_match call using where create create_unique merge set on_create_set on_match_set remove unwind delete detach_delete with with_distinct return order skip limit] # rubocop:disable Metrics/LineLength
166
- BREAK_METHODS = %(with with_distinct call)
165
+ METHODS = %w[start match optional_match call using where create create_unique merge set on_create_set on_match_set remove unwind delete detach_delete with with_distinct return order skip limit union call_subquery_start call_subquery_end] # rubocop:disable Layout/LineLength
166
+ BREAK_METHODS = %w(with with_distinct call call_subquery_start call_subquery_end)
167
167
 
168
168
  CLAUSIFY_CLAUSE = proc { |method| const_get(method.to_s.split('_').map(&:capitalize).join + 'Clause') }
169
169
  CLAUSES = METHODS.map(&CLAUSIFY_CLAUSE)
@@ -28,7 +28,7 @@ module ActiveGraph
28
28
  def value
29
29
  return @value if @value
30
30
 
31
- [String, Symbol, Integer, Hash, NilClass].each do |arg_class|
31
+ [String, Symbol, Integer, Hash, NilClass, Array].each do |arg_class|
32
32
  from_method = "from_#{arg_class.name.downcase}"
33
33
  return @value = send(from_method, @arg) if @arg.is_a?(arg_class) && respond_to?(from_method)
34
34
  end
@@ -130,13 +130,15 @@ module ActiveGraph
130
130
  def to_cypher(clauses, pretty = false)
131
131
  string = clause_string(clauses, pretty)
132
132
 
133
- final_keyword = if pretty
134
- "#{clause_color}#{keyword}#{ANSI::CLEAR}"
135
- else
136
- keyword
137
- end
133
+ "#{final_keyword(pretty)} #{string}" if !string.empty?
134
+ end
138
135
 
139
- "#{final_keyword} #{string}" if !string.empty?
136
+ def final_keyword(pretty)
137
+ if pretty
138
+ "#{clause_color}#{keyword}#{ANSI::CLEAR}"
139
+ else
140
+ keyword
141
+ end
140
142
  end
141
143
 
142
144
  def clause_string(clauses, pretty)
@@ -256,6 +258,30 @@ module ActiveGraph
256
258
  end
257
259
  end
258
260
 
261
+ class UnionClause < Clause
262
+ KEYWORD = ''
263
+
264
+ def from_array(args)
265
+ "#{args[1]} RETURN #{args[0]} AS #{args.last}"
266
+ end
267
+
268
+ class << self
269
+ def from_args(args, params, options = {})
270
+ params.add_params(args[2])
271
+
272
+ [from_arg(args, params, options)]
273
+ end
274
+
275
+ def clause_strings(clauses)
276
+ clauses.map!(&:value)
277
+ end
278
+
279
+ def clause_join
280
+ ' UNION '
281
+ end
282
+ end
283
+ end
284
+
259
285
  class WhereClause < Clause
260
286
  KEYWORD = 'WHERE'
261
287
 
@@ -349,6 +375,38 @@ module ActiveGraph
349
375
  end
350
376
  end
351
377
 
378
+ class CallSubqueryStartClause < Clause
379
+ KEYWORD = 'CALL {'
380
+
381
+ def from_nilclass(_value)
382
+ ' '
383
+ end
384
+
385
+ def from_string(value)
386
+ value
387
+ end
388
+
389
+ class << self
390
+ def to_cypher(clauses, pretty = false)
391
+ super || final_keyword(pretty)
392
+ end
393
+
394
+ def clause_strings(clauses)
395
+ clauses.map!(&:value)
396
+ end
397
+ end
398
+ end
399
+
400
+ class CallSubqueryEndClause < Clause
401
+ KEYWORD = '}'
402
+
403
+ class << self
404
+ def to_cypher(_clauses, pretty = false)
405
+ final_keyword(pretty)
406
+ end
407
+ end
408
+ end
409
+
352
410
  class MatchClause < Clause
353
411
  KEYWORD = 'MATCH'
354
412
 
@@ -94,13 +94,25 @@ module ActiveGraph
94
94
  #
95
95
  # student.lessons.query_as(:l).with('your cypher here...')
96
96
  def query_as(var, with_labels = true)
97
- query_from_chain(chain, base_query(var, with_labels).params(@params), var)
98
- .tap { |query| query.proxy_chain_level = _chain_level }
97
+ init_outer_query_var(var)
98
+ var_name = @outer_query_var || var
99
+ query_obj = if chain.first&.start_of_subquery? && !@association && !starting_query
100
+ _query
101
+ else
102
+ base_query(var_name, with_labels).params(@params)
103
+ end
104
+
105
+ query_from_chain(chain, query_obj, var_name).tap { |query| query.proxy_chain_level = _chain_level }
106
+ end
107
+
108
+ def init_outer_query_var(var)
109
+ chain.find(&:start_of_subquery?)&.tap { |link| @outer_query_var = link.subquery_var(var) }
99
110
  end
100
111
 
101
112
  def query_from_chain(chain, base_query, var)
102
113
  chain.inject(base_query) do |query, link|
103
114
  args = link.args(var, rel_var)
115
+ var = link.update_outer_query_var(var)
104
116
 
105
117
  args.is_a?(Array) ? query.send(link.clause, *args) : query.send(link.clause, args)
106
118
  end
@@ -143,11 +155,16 @@ module ActiveGraph
143
155
  @model.current_scope = previous
144
156
  end
145
157
 
146
- METHODS = %w(where where_not rel_where rel_where_not rel_order order skip limit)
158
+ METHODS = %w(where where_not rel_where rel_where_not rel_order order skip limit union)
147
159
 
148
160
  METHODS.each do |method|
149
161
  define_method(method) { |*args| build_deeper_query_proxy(method.to_sym, args) }
150
162
  end
163
+
164
+ def union(*args)
165
+ hash_args = {proxy: self, subquery_parts: args, first_clause: @chain.blank?}
166
+ build_deeper_query_proxy(:union, hash_args)
167
+ end
151
168
  # Since there are rel_where and rel_order methods, it seems only natural for there to be node_where and node_order
152
169
  alias node_where where
153
170
  alias node_order order
@@ -3,6 +3,7 @@ module ActiveGraph
3
3
  module Query
4
4
  class QueryProxy
5
5
  class Link
6
+ OUTER_SUBQUERY_PREFIX = 'outer_'.freeze
6
7
  attr_reader :clause
7
8
 
8
9
  def initialize(clause, arg, args = [])
@@ -19,6 +20,26 @@ module ActiveGraph
19
20
  end
20
21
  end
21
22
 
23
+ def start_of_subquery?
24
+ clause == :call_subquery_start
25
+ end
26
+
27
+ def end_of_subquery?
28
+ clause == :call_subquery_end
29
+ end
30
+
31
+ def subquery_var(original_var)
32
+ return unless start_of_subquery?
33
+
34
+ "#{OUTER_SUBQUERY_PREFIX}#{original_var}"
35
+ end
36
+
37
+ def update_outer_query_var(original_var)
38
+ return original_var unless end_of_subquery?
39
+
40
+ original_var.delete_prefix(OUTER_SUBQUERY_PREFIX)
41
+ end
42
+
22
43
  class << self
23
44
  def for_clause(clause, arg, model, *args)
24
45
  method_to_call = "for_#{clause}_clause"
@@ -27,6 +48,75 @@ module ActiveGraph
27
48
  send(method_to_call, arg, model, *args)
28
49
  end
29
50
 
51
+ def for_union_clause(arg, model, *args)
52
+ links = []
53
+ links << new(:call_subquery_start, nil, *args)
54
+ arg[:subquery_parts].each_with_index do |subquery_part, loop_index|
55
+ links << init_union_link(arg[:proxy], model, subquery_part, loop_index, args)
56
+ end
57
+ links << new(:call_subquery_end, nil, *args)
58
+ links << post_subquery_with_clause(arg[:first_clause], args)
59
+ end
60
+
61
+ def post_subquery_with_clause(first_clause, args)
62
+ clause_arg_lambda = lambda do |v, _|
63
+ if first_clause
64
+ [v]
65
+ else
66
+ [v, "#{OUTER_SUBQUERY_PREFIX}#{v}"]
67
+ end
68
+ end
69
+
70
+ new(:with, clause_arg_lambda, *args)
71
+ end
72
+
73
+ def init_union_link(proxy, model, subquery_part, loop_index, args)
74
+ union_proc = if subquery_proxy_part = subquery_part.call rescue nil # rubocop:disable Style/RescueModifier
75
+ independent_union_subquery_proc(subquery_proxy_part, loop_index)
76
+ else
77
+ continuation_union_subquery_proc(proxy, model, subquery_part, loop_index)
78
+ end
79
+ new(:union, union_proc, *args)
80
+ end
81
+
82
+ def independent_union_subquery_proc(proxy, loop_index)
83
+ proxy_params = proxy.query.parameters
84
+ proxy_cypher = proxy.to_cypher
85
+ subquery_identity = proxy.identity
86
+ uniq_param_generator = uniq_param_generator_lambda
87
+
88
+ lambda do |identity, _|
89
+ proxy_params = uniq_param_generator.call(proxy_params, proxy_cypher, identity, loop_index) if proxy_params.present?
90
+ [subquery_identity, proxy_cypher, proxy_params, identity.delete_prefix(OUTER_SUBQUERY_PREFIX)]
91
+ end
92
+ end
93
+
94
+ def continuation_union_subquery_proc(outer_proxy, model, subquery_part, loop_index) # rubocop:disable Metrics/AbcSize
95
+ lambda do |identity, _|
96
+ proxy = outer_proxy.as(identity)
97
+ proxy_with_clause = proxy.query.with(proxy.identity).with(proxy.identity).proxy_as(model, proxy.identity)
98
+ complete_query = proxy_with_clause.instance_exec(&subquery_part) || proxy_with_clause
99
+ subquery_cypher = complete_query.to_cypher
100
+ subquery_cypher = subquery_cypher.delete_prefix(proxy.to_cypher) if proxy.send(:chain).present? || proxy.starting_query
101
+ subquery_parameters = (complete_query.query.parameters.to_a - proxy.query.parameters.to_a).to_h
102
+
103
+ subquery_parameters = uniq_param_generator_lambda.call(subquery_parameters, subquery_cypher, identity, loop_index) if subquery_parameters.present?
104
+ [complete_query.identity, subquery_cypher, subquery_parameters] + [identity.delete_prefix(OUTER_SUBQUERY_PREFIX)]
105
+ end
106
+ end
107
+
108
+ def uniq_param_generator_lambda
109
+ lambda do |proxy_params, proxy_cypher, identity, counter|
110
+ prefix = "#{identity}_UNION#{counter}_"
111
+ proxy_params.each_with_object({}) do |(param_name, param_val), new_params|
112
+ new_params_key = "#{prefix}#{param_name}".to_sym
113
+ new_params[new_params_key] = param_val
114
+ proxy_cypher.gsub!(/\$#{param_name}(?=([^`'"]|'(\\.|[^'])*'|"(\\.|[^"])*"|`(\\.|[^`])*`)*\z)/, "$#{new_params_key}")
115
+ new_params
116
+ end
117
+ end
118
+ end
119
+
30
120
  def for_where_clause(arg, model, *args)
31
121
  node_num = 1
32
122
  result = []
@@ -104,6 +194,8 @@ module ActiveGraph
104
194
  [for_arg(model, clause, args[0], *args[1..-1])]
105
195
  elsif [:rel_where, :rel_where_not].include?(clause)
106
196
  args.map { |arg| for_arg(model, clause, arg, association) }
197
+ elsif clause == :union
198
+ [for_arg(model, clause, args)]
107
199
  else
108
200
  args.map { |arg| for_arg(model, clause, arg) }
109
201
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveGraph
2
- VERSION = '11.3.1'
2
+ VERSION = '11.4.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activegraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.3.1
4
+ version: 11.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andreas Ronge, Brian Underwood, Chris Grigg, Heinrich Klobuczek
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-13 00:00:00.000000000 Z
11
+ date: 2023-09-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -444,7 +444,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
444
444
  - !ruby/object:Gem::Version
445
445
  version: '0'
446
446
  requirements: []
447
- rubygems_version: 3.3.26
447
+ rubygems_version: 3.3.7
448
448
  signing_key:
449
449
  specification_version: 4
450
450
  summary: A graph database for Ruby