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 +4 -4
- data/CHANGELOG.md +6 -0
- data/lib/active_graph/core/query.rb +2 -2
- data/lib/active_graph/core/query_clauses.rb +65 -7
- data/lib/active_graph/node/query/query_proxy.rb +20 -3
- data/lib/active_graph/node/query/query_proxy_link.rb +92 -0
- data/lib/active_graph/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f068da2126bbed4301cf9fb15c34a708ef7f7a152afbfe42af5482237a041f0d
|
4
|
+
data.tar.gz: 0c87c4c2082a2e999ac8e0ff165e0cdbffe875f7f2eae407ea31f3f119d46a5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
134
|
-
|
135
|
-
else
|
136
|
-
keyword
|
137
|
-
end
|
133
|
+
"#{final_keyword(pretty)} #{string}" if !string.empty?
|
134
|
+
end
|
138
135
|
|
139
|
-
|
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
|
-
|
98
|
-
|
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
|
data/lib/active_graph/version.rb
CHANGED
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.
|
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-
|
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.
|
447
|
+
rubygems_version: 3.3.7
|
448
448
|
signing_key:
|
449
449
|
specification_version: 4
|
450
450
|
summary: A graph database for Ruby
|