activegraph 11.3.1 → 11.4.0

Sign up to get free protection for your applications and to get access to all the features.
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