cypher-dsl 0.1.0.alpha.1 → 0.1.0.alpha.2
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 +53 -1
- data/README.md +53 -4
- data/lib/cypher_dsl/clauses/load_csv.rb +8 -2
- data/lib/cypher_dsl/clauses/match.rb +2 -2
- data/lib/cypher_dsl/clauses.rb +11 -0
- data/lib/cypher_dsl/comparison.rb +6 -0
- data/lib/cypher_dsl/count_expression.rb +7 -3
- data/lib/cypher_dsl/cypher.rb +80 -19
- data/lib/cypher_dsl/expression.rb +3 -0
- data/lib/cypher_dsl/function_invocation.rb +7 -0
- data/lib/cypher_dsl/functions.rb +11 -1
- data/lib/cypher_dsl/has_label_condition.rb +1 -1
- data/lib/cypher_dsl/hint.rb +71 -0
- data/lib/cypher_dsl/label_operation.rb +11 -3
- data/lib/cypher_dsl/labels.rb +66 -0
- data/lib/cypher_dsl/literal.rb +24 -2
- data/lib/cypher_dsl/named_path.rb +6 -3
- data/lib/cypher_dsl/node.rb +15 -4
- data/lib/cypher_dsl/property.rb +9 -2
- data/lib/cypher_dsl/property_exists_condition.rb +15 -0
- data/lib/cypher_dsl/raw_expression.rb +76 -3
- data/lib/cypher_dsl/relationship.rb +8 -1
- data/lib/cypher_dsl/relationship_chain.rb +3 -0
- data/lib/cypher_dsl/renderer/configuration.rb +61 -4
- data/lib/cypher_dsl/renderer/dialect.rb +24 -7
- data/lib/cypher_dsl/renderer/escaper.rb +10 -7
- data/lib/cypher_dsl/renderer/generated_name_resolver.rb +110 -0
- data/lib/cypher_dsl/renderer/name_resolver.rb +35 -0
- data/lib/cypher_dsl/renderer/visitor/expressions.rb +52 -15
- data/lib/cypher_dsl/renderer/visitor/label_rendering.rb +84 -0
- data/lib/cypher_dsl/renderer/visitor/patterns.rb +17 -29
- data/lib/cypher_dsl/renderer/visitor.rb +99 -27
- data/lib/cypher_dsl/renderer.rb +11 -5
- data/lib/cypher_dsl/search.rb +91 -0
- data/lib/cypher_dsl/standalone_call.rb +0 -6
- data/lib/cypher_dsl/statement.rb +4 -0
- data/lib/cypher_dsl/statement_builder.rb +67 -14
- data/lib/cypher_dsl/statement_catalog.rb +40 -6
- data/lib/cypher_dsl/subquery_with.rb +1 -0
- data/lib/cypher_dsl/symbolic_name.rb +1 -1
- data/lib/cypher_dsl/using_periodic_commit.rb +11 -0
- data/lib/cypher_dsl/vector_literal.rb +41 -0
- data/lib/cypher_dsl/version.rb +1 -1
- metadata +11 -3
- data/lib/cypher_dsl/label_expression.rb +0 -40
|
@@ -6,8 +6,6 @@ module CypherDSL
|
|
|
6
6
|
# `build` finishes a bare call, `as_function` turns it into a function
|
|
7
7
|
# invocation instead.
|
|
8
8
|
class StandaloneCall
|
|
9
|
-
AGGREGATES = %w[avg collect count max min percentilecont percentiledisc stdev stdevp sum].freeze
|
|
10
|
-
|
|
11
9
|
attr_reader :name_parts, :arguments
|
|
12
10
|
|
|
13
11
|
# `name_parts` as given to `Cypher.call`: strings, symbols or arrays, dotted names split.
|
|
@@ -37,10 +35,6 @@ module CypherDSL
|
|
|
37
35
|
|
|
38
36
|
# The procedure as a function invocation; `distinct` only for aggregates.
|
|
39
37
|
def as_function(distinct = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
40
|
-
if distinct && !AGGREGATES.include?(procedure_name.downcase)
|
|
41
|
-
raise ArgumentError, 'The distinct operator can only be applied within aggregate functions.'
|
|
42
|
-
end
|
|
43
|
-
|
|
44
38
|
FunctionInvocation.new(procedure_name, arguments, distinct:)
|
|
45
39
|
end
|
|
46
40
|
|
data/lib/cypher_dsl/statement.rb
CHANGED
|
@@ -23,6 +23,10 @@ module CypherDSL
|
|
|
23
23
|
@cypher ||= Renderer.default.render(self).freeze
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
# The same statement with `EXPLAIN` / `PROFILE` in front.
|
|
27
|
+
def explain = Statement.new(clauses, prefix: 'EXPLAIN')
|
|
28
|
+
def profile = Statement.new(clauses, prefix: 'PROFILE')
|
|
29
|
+
|
|
26
30
|
# Parameter names and bound values; raises {ConflictingParametersException}
|
|
27
31
|
# when a name is bound inconsistently. Memoised, like `cypher`.
|
|
28
32
|
def catalog
|
|
@@ -19,11 +19,32 @@ module CypherDSL
|
|
|
19
19
|
# `match(pattern, …)` or, as in Java, `match(optional, pattern, …)` with a leading flag.
|
|
20
20
|
def match(*pattern_elements)
|
|
21
21
|
optional = [true, false].include?(pattern_elements.first) ? pattern_elements.shift : false
|
|
22
|
-
|
|
22
|
+
raise ArgumentError, 'Patterns to match are required.' if pattern_elements.any?(&:nil?)
|
|
23
|
+
|
|
24
|
+
pattern_elements = pattern_elements.flatten.freeze
|
|
25
|
+
raise ArgumentError, 'At least one pattern to match is required.' if pattern_elements.empty?
|
|
26
|
+
|
|
27
|
+
append(Clauses::Match.new(optional:, pattern_elements:, hints: [].freeze, search: nil, where: nil), :condition)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A vector `SEARCH` on the preceding MATCH; see {Search}.
|
|
31
|
+
def search(search)
|
|
32
|
+
update_last(:condition) do |clause|
|
|
33
|
+
raise ArgumentError, 'SEARCH needs a preceding MATCH.' unless clause.is_a?(Clauses::Match)
|
|
34
|
+
|
|
35
|
+
clause.with(search:)
|
|
36
|
+
end
|
|
23
37
|
end
|
|
24
38
|
|
|
25
39
|
def optional_match(*pattern_elements) = match(true, *pattern_elements)
|
|
26
40
|
|
|
41
|
+
# Planner hints on the preceding MATCH: `using_index(n.property(:name))`,
|
|
42
|
+
# `using_index_seek(…)`, `using_scan(n)`, `using_join_on(n, m)`.
|
|
43
|
+
def using_index(*properties) = add_hint(Hint.use_index_for(false, *properties))
|
|
44
|
+
def using_index_seek(*properties) = add_hint(Hint.use_index_for(true, *properties))
|
|
45
|
+
def using_scan(node) = add_hint(Hint.use_scan_for(node))
|
|
46
|
+
def using_join_on(*nodes_or_names) = add_hint(Hint.use_join_on(*nodes_or_names))
|
|
47
|
+
|
|
27
48
|
def where(condition)
|
|
28
49
|
if @clauses.last.is_a?(Clauses::CallSubquery)
|
|
29
50
|
raise ArgumentError, 'A CALL{} clause requires to WITH before you can add further conditions'
|
|
@@ -52,15 +73,19 @@ module CypherDSL
|
|
|
52
73
|
def returning_raw(*expressions) = append(Clauses::RawReturn.new(items: coerce_items(expressions)), :projection)
|
|
53
74
|
|
|
54
75
|
def with(*expressions)
|
|
55
|
-
append(Clauses::With.new(distinct: false, items:
|
|
56
|
-
where: nil), :condition)
|
|
76
|
+
append(Clauses::With.new(distinct: false, items: projection_items(expressions), order: nil, skip: nil,
|
|
77
|
+
limit: nil, where: nil), :condition)
|
|
57
78
|
end
|
|
58
79
|
|
|
59
80
|
def with_distinct(*expressions)
|
|
60
|
-
append(Clauses::With.new(distinct: true, items:
|
|
61
|
-
where: nil), :condition)
|
|
81
|
+
append(Clauses::With.new(distinct: true, items: projection_items(expressions), order: nil, skip: nil,
|
|
82
|
+
limit: nil, where: nil), :condition)
|
|
62
83
|
end
|
|
63
84
|
|
|
85
|
+
# The names and expressions in scope after the last WITH or RETURN, as
|
|
86
|
+
# `getIdentifiableExpressions()` on the Java builder states.
|
|
87
|
+
def identifiable_expressions = StatementCatalog.identifiable_expressions_of(build)
|
|
88
|
+
|
|
64
89
|
def order_by(*sort_items)
|
|
65
90
|
update_last(:order) { |clause| clause.with(order: Clauses::Order.new(items: coerce_sort_items(sort_items))) }
|
|
66
91
|
end
|
|
@@ -136,7 +161,18 @@ module CypherDSL
|
|
|
136
161
|
end
|
|
137
162
|
|
|
138
163
|
# `load_csv('file:///x.csv').as(:line)`.
|
|
139
|
-
def load_csv(uri,
|
|
164
|
+
def load_csv(uri, positional_headers = false, with_headers: positional_headers, periodic_commit: nil) # rubocop:disable Style/OptionalBooleanParameter
|
|
165
|
+
OngoingLoadCsv.new(self, uri.to_s, with_headers, periodic_commit)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# `FIELDTERMINATOR ';'` on the preceding LOAD CSV.
|
|
169
|
+
def with_field_terminator(terminator)
|
|
170
|
+
update_last(nil) do |clause|
|
|
171
|
+
raise ArgumentError, 'FIELDTERMINATOR needs a preceding LOAD CSV.' unless clause.is_a?(Clauses::LoadCsv)
|
|
172
|
+
|
|
173
|
+
clause.with_field_terminator(terminator&.to_s)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
140
176
|
|
|
141
177
|
# The statement built so far as an `EXISTS { … }` condition.
|
|
142
178
|
def as_condition = ExistsCondition.new(build)
|
|
@@ -176,23 +212,22 @@ module CypherDSL
|
|
|
176
212
|
|
|
177
213
|
def in(list) = OngoingForeach.new(@builder, @variable, Expression.coerce(list))
|
|
178
214
|
|
|
179
|
-
def apply(*clauses)
|
|
180
|
-
foreach = Clauses::Foreach.new(variable: @variable, list: @list, clauses: clauses.flatten.freeze)
|
|
181
|
-
@builder.send(:append, foreach, nil)
|
|
182
|
-
end
|
|
215
|
+
def apply(*clauses) = @builder.send(:append, Clauses.for_each(@variable, @list, clauses.flatten), nil)
|
|
183
216
|
end
|
|
184
217
|
|
|
185
218
|
# Intermediate state of `load_csv(uri)` waiting for `as(alias)`.
|
|
186
219
|
class OngoingLoadCsv
|
|
187
|
-
def initialize(builder, uri, with_headers)
|
|
220
|
+
def initialize(builder, uri, with_headers, periodic_commit)
|
|
188
221
|
@builder = builder
|
|
189
222
|
@uri = uri
|
|
190
223
|
@with_headers = with_headers
|
|
224
|
+
@periodic_commit = periodic_commit
|
|
191
225
|
freeze
|
|
192
226
|
end
|
|
193
227
|
|
|
194
228
|
def as(alias_name)
|
|
195
|
-
clause = Clauses::LoadCsv.new(uri: @uri, with_headers: @with_headers, alias_name: SymbolicName.of(alias_name)
|
|
229
|
+
clause = Clauses::LoadCsv.new(uri: @uri, with_headers: @with_headers, alias_name: SymbolicName.of(alias_name),
|
|
230
|
+
field_terminator: nil, periodic_commit: @periodic_commit)
|
|
196
231
|
@builder.send(:append, clause, nil)
|
|
197
232
|
end
|
|
198
233
|
end
|
|
@@ -220,6 +255,14 @@ module CypherDSL
|
|
|
220
255
|
Clauses::CallSubquery.new(body: statement, imports: coerce_items(imports), in_transactions:, rows:)
|
|
221
256
|
end
|
|
222
257
|
|
|
258
|
+
def add_hint(hint)
|
|
259
|
+
update_last(:condition) do |clause|
|
|
260
|
+
raise ArgumentError, 'Hints need a preceding MATCH.' unless clause.is_a?(Clauses::Match)
|
|
261
|
+
|
|
262
|
+
clause.with(hints: clause.hints + [hint])
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
223
266
|
def add_merge_action(type, operations)
|
|
224
267
|
update_last(nil) do |clause|
|
|
225
268
|
raise ArgumentError, 'ON CREATE / ON MATCH need a preceding MERGE.' unless clause.is_a?(Clauses::Merge)
|
|
@@ -244,7 +287,7 @@ module CypherDSL
|
|
|
244
287
|
|
|
245
288
|
def label_operation?(arguments)
|
|
246
289
|
arguments.size > 1 && arguments.first.is_a?(Node) &&
|
|
247
|
-
arguments.drop(1).all? { |argument|
|
|
290
|
+
arguments.drop(1).all? { |argument| [String, Symbol, Labels].any? { |type| argument.is_a?(type) } }
|
|
248
291
|
end
|
|
249
292
|
|
|
250
293
|
def optional_expression(value) = value.nil? ? nil : Expression.coerce(value)
|
|
@@ -256,7 +299,17 @@ module CypherDSL
|
|
|
256
299
|
end
|
|
257
300
|
|
|
258
301
|
def return_clause(distinct, expressions)
|
|
259
|
-
Clauses::Return.new(distinct:, items:
|
|
302
|
+
Clauses::Return.new(distinct:, items: projection_items(expressions), order: nil, skip: nil, limit: nil)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# RETURN/WITH items with the upstream preconditions.
|
|
306
|
+
def projection_items(expressions)
|
|
307
|
+
raise ArgumentError, 'Expressions to return are required.' if expressions.any?(&:nil?)
|
|
308
|
+
|
|
309
|
+
items = coerce_items(expressions)
|
|
310
|
+
raise ArgumentError, 'At least one expressions to return is required.' if items.empty?
|
|
311
|
+
|
|
312
|
+
items
|
|
260
313
|
end
|
|
261
314
|
|
|
262
315
|
def add_sort_item(expression)
|
|
@@ -1,24 +1,58 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module CypherDSL
|
|
4
|
-
# What a statement uses: its parameter names,
|
|
5
|
-
#
|
|
4
|
+
# What a statement uses: its parameter names, the values of the parameters
|
|
5
|
+
# that carry one, and the expressions it hands back. Built from a rendering pass.
|
|
6
6
|
class StatementCatalog
|
|
7
|
-
attr_reader :parameters, :parameter_names
|
|
7
|
+
attr_reader :parameters, :parameter_names, :renamed_parameters, :identifiable_expressions
|
|
8
8
|
|
|
9
9
|
# `uses` maps each parameter name to every value it was given, in render
|
|
10
10
|
# order, with {Parameter::NO_VALUE} for unbound uses.
|
|
11
|
-
def self.from_uses(uses)
|
|
11
|
+
def self.from_uses(uses, renamed_parameters = {}, statement: nil)
|
|
12
12
|
conflicts = uses.filter_map { |name, values| [name, values.uniq.to_set] if values.uniq.size > 1 }.to_h
|
|
13
13
|
raise ConflictingParametersException, conflicts unless conflicts.empty?
|
|
14
14
|
|
|
15
15
|
bound = uses.filter_map { |name, values| [name, values.first] unless values.first.equal?(Parameter::NO_VALUE) }
|
|
16
|
-
new(parameters: bound.to_h, parameter_names: uses.keys
|
|
16
|
+
new(parameters: bound.to_h, parameter_names: uses.keys, renamed_parameters:,
|
|
17
|
+
identifiable_expressions: identifiable_expressions_of(statement))
|
|
17
18
|
end
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
# The expressions a statement hands back to its caller: the identifiable items of
|
|
21
|
+
# its final RETURN or WITH (names for aliased items and named entities, properties
|
|
22
|
+
# as they are), or the names a bare procedure call yields. Empty otherwise.
|
|
23
|
+
def self.identifiable_expressions_of(statement)
|
|
24
|
+
case statement
|
|
25
|
+
when UnionQuery then statement.statements.flat_map { |part| identifiable_expressions_of(part) }.uniq
|
|
26
|
+
when Statement then result_items_of(statement.clauses.last).filter_map { |item| identifiable_expression(item) }
|
|
27
|
+
else []
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.result_items_of(clause)
|
|
32
|
+
case clause
|
|
33
|
+
when Clauses::Return, Clauses::With then clause.items
|
|
34
|
+
when Clauses::Call then clause.yield_items
|
|
35
|
+
else []
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
private_class_method :result_items_of
|
|
39
|
+
|
|
40
|
+
# Only names, aliases, named entities and properties identify something; a literal does not.
|
|
41
|
+
def self.identifiable_expression(item)
|
|
42
|
+
case item
|
|
43
|
+
when AliasedExpression then item.alias_name
|
|
44
|
+
when Node, Relationship then item.symbolic_name
|
|
45
|
+
when SymbolicName, Property then item
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
private_class_method :identifiable_expression
|
|
49
|
+
|
|
50
|
+
# `renamed_parameters` maps original names to generated ones when the renderer rewrites them.
|
|
51
|
+
def initialize(parameters:, parameter_names:, renamed_parameters: {}, identifiable_expressions: [])
|
|
20
52
|
@parameters = parameters.freeze
|
|
21
53
|
@parameter_names = parameter_names.freeze
|
|
54
|
+
@renamed_parameters = renamed_parameters.freeze
|
|
55
|
+
@identifiable_expressions = identifiable_expressions.freeze
|
|
22
56
|
freeze
|
|
23
57
|
end
|
|
24
58
|
end
|
|
@@ -10,6 +10,7 @@ module CypherDSL
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def collect(statement) = CollectExpression.new(statement, @imports)
|
|
13
|
+
def count(statement_or_pattern) = CountExpression.new(statement_or_pattern, @imports)
|
|
13
14
|
def exists(statement_or_pattern) = ExistsCondition.new(statement_or_pattern, @imports)
|
|
14
15
|
end
|
|
15
16
|
end
|
|
@@ -15,7 +15,7 @@ module CypherDSL
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
# A name to be generated at render time; `hint` seeds the generated name (letters only).
|
|
18
|
-
def self.unresolved(hint) = new(nil, hint: hint.to_s.gsub(/[^a-zA-Z]/, ''))
|
|
18
|
+
def self.unresolved(hint = '') = new(nil, hint: hint.to_s.gsub(/[^a-zA-Z]/, ''))
|
|
19
19
|
|
|
20
20
|
def initialize(value, hint: nil)
|
|
21
21
|
raise ArgumentError, 'A symbolic name must not be empty.' if value && value.empty?
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CypherDSL
|
|
4
|
+
# `Cypher.using_periodic_commit(500).load_csv(uri).as(:line)`: the legacy
|
|
5
|
+
# `USING PERIODIC COMMIT [rate]` prefix of a LOAD CSV statement.
|
|
6
|
+
UsingPeriodicCommit = Data.define(:rate) do
|
|
7
|
+
def load_csv(uri, positional_headers = false, with_headers: positional_headers) # rubocop:disable Style/OptionalBooleanParameter
|
|
8
|
+
StatementBuilder.new.load_csv(uri, with_headers:, periodic_commit: rate || true)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CypherDSL
|
|
4
|
+
# `vector([1, 2, 3], 3, INTEGER NOT NULL)`: a typed vector literal. The Java
|
|
5
|
+
# overloads per primitive array become an optional type: `:integer8`,
|
|
6
|
+
# `:integer16`, `:integer32`, `:integer` (default for integers), `:float32`
|
|
7
|
+
# and `:float` (default for floats).
|
|
8
|
+
class VectorLiteral
|
|
9
|
+
include Expression
|
|
10
|
+
|
|
11
|
+
TYPES = { integer8: 'INTEGER8', integer16: 'INTEGER16', integer32: 'INTEGER32', integer: 'INTEGER',
|
|
12
|
+
float32: 'FLOAT32', float: 'FLOAT' }.freeze
|
|
13
|
+
FLOAT_TYPES = %i[float32 float].freeze
|
|
14
|
+
MAX_SIZE = 4096
|
|
15
|
+
|
|
16
|
+
attr_reader :content, :type
|
|
17
|
+
|
|
18
|
+
def self.of(elements, type = nil)
|
|
19
|
+
raise ArgumentError, 'Vector elements must not be literal null' if elements.nil?
|
|
20
|
+
|
|
21
|
+
size = elements.size
|
|
22
|
+
unless (1..MAX_SIZE).cover?(size)
|
|
23
|
+
raise ArgumentError,
|
|
24
|
+
"'#{size}' is not a valid value. Must be a number in the range 1 to #{MAX_SIZE} (GQL 42N31)"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
new(elements, type || (elements.all?(Integer) ? :integer : :float))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(content, type)
|
|
31
|
+
raise ArgumentError, "Unknown vector type #{type.inspect}" unless TYPES.key?(type)
|
|
32
|
+
|
|
33
|
+
@type = type
|
|
34
|
+
@content = (FLOAT_TYPES.include?(type) ? content.map(&:to_f) : content.map(&:to_i)).freeze
|
|
35
|
+
freeze
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# The literal as Cypher text; it does not depend on any rendering option.
|
|
39
|
+
def as_string = "vector([#{content.join(', ')}], #{content.size}, #{TYPES[type]} NOT NULL)"
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/cypher_dsl/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cypher-dsl
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.0.alpha.
|
|
4
|
+
version: 0.1.0.alpha.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Heinrich Klobuczek
|
|
@@ -71,8 +71,9 @@ files:
|
|
|
71
71
|
- lib/cypher_dsl/function_invocation.rb
|
|
72
72
|
- lib/cypher_dsl/functions.rb
|
|
73
73
|
- lib/cypher_dsl/has_label_condition.rb
|
|
74
|
-
- lib/cypher_dsl/
|
|
74
|
+
- lib/cypher_dsl/hint.rb
|
|
75
75
|
- lib/cypher_dsl/label_operation.rb
|
|
76
|
+
- lib/cypher_dsl/labels.rb
|
|
76
77
|
- lib/cypher_dsl/list_comprehension.rb
|
|
77
78
|
- lib/cypher_dsl/list_expression.rb
|
|
78
79
|
- lib/cypher_dsl/list_operator.rb
|
|
@@ -89,6 +90,7 @@ files:
|
|
|
89
90
|
- lib/cypher_dsl/pattern_comprehension.rb
|
|
90
91
|
- lib/cypher_dsl/pattern_expression.rb
|
|
91
92
|
- lib/cypher_dsl/property.rb
|
|
93
|
+
- lib/cypher_dsl/property_exists_condition.rb
|
|
92
94
|
- lib/cypher_dsl/quantified_path_pattern.rb
|
|
93
95
|
- lib/cypher_dsl/raw_expression.rb
|
|
94
96
|
- lib/cypher_dsl/reduction.rb
|
|
@@ -99,9 +101,13 @@ files:
|
|
|
99
101
|
- lib/cypher_dsl/renderer/configuration.rb
|
|
100
102
|
- lib/cypher_dsl/renderer/dialect.rb
|
|
101
103
|
- lib/cypher_dsl/renderer/escaper.rb
|
|
104
|
+
- lib/cypher_dsl/renderer/generated_name_resolver.rb
|
|
105
|
+
- lib/cypher_dsl/renderer/name_resolver.rb
|
|
102
106
|
- lib/cypher_dsl/renderer/visitor.rb
|
|
103
107
|
- lib/cypher_dsl/renderer/visitor/expressions.rb
|
|
108
|
+
- lib/cypher_dsl/renderer/visitor/label_rendering.rb
|
|
104
109
|
- lib/cypher_dsl/renderer/visitor/patterns.rb
|
|
110
|
+
- lib/cypher_dsl/search.rb
|
|
105
111
|
- lib/cypher_dsl/sort_item.rb
|
|
106
112
|
- lib/cypher_dsl/standalone_call.rb
|
|
107
113
|
- lib/cypher_dsl/statement.rb
|
|
@@ -111,6 +117,8 @@ files:
|
|
|
111
117
|
- lib/cypher_dsl/symbolic_name.rb
|
|
112
118
|
- lib/cypher_dsl/unary_operation.rb
|
|
113
119
|
- lib/cypher_dsl/union_query.rb
|
|
120
|
+
- lib/cypher_dsl/using_periodic_commit.rb
|
|
121
|
+
- lib/cypher_dsl/vector_literal.rb
|
|
114
122
|
- lib/cypher_dsl/version.rb
|
|
115
123
|
homepage: https://github.com/neo4jrb/cypher-dsl
|
|
116
124
|
licenses:
|
|
@@ -134,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
134
142
|
- !ruby/object:Gem::Version
|
|
135
143
|
version: '0'
|
|
136
144
|
requirements: []
|
|
137
|
-
rubygems_version: 4.0.
|
|
145
|
+
rubygems_version: 4.0.3
|
|
138
146
|
specification_version: 4
|
|
139
147
|
summary: A Ruby DSL (builder) for the Cypher query language
|
|
140
148
|
test_files: []
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module CypherDSL
|
|
4
|
-
# A label expression as in `(n:A|B)`, `(n:A&!B)` or `(n:(A|B)&C)`; built from
|
|
5
|
-
# {Labels.exactly} and combined with {#or}, {#and} and {#negate}.
|
|
6
|
-
class LabelExpression
|
|
7
|
-
TYPES = %i[leaf conjunction disjunction negation].freeze
|
|
8
|
-
|
|
9
|
-
attr_reader :type, :value, :operands
|
|
10
|
-
|
|
11
|
-
def self.exactly(name) = new(:leaf, name.to_s, [])
|
|
12
|
-
|
|
13
|
-
def initialize(type, value, operands)
|
|
14
|
-
raise ArgumentError, "Unknown label expression type #{type.inspect}" unless TYPES.include?(type)
|
|
15
|
-
|
|
16
|
-
@type = type
|
|
17
|
-
@value = value
|
|
18
|
-
@operands = operands.freeze
|
|
19
|
-
freeze
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def or(other) = LabelExpression.new(:disjunction, nil, [self, coerce(other)])
|
|
23
|
-
def and(other) = LabelExpression.new(:conjunction, nil, [self, coerce(other)])
|
|
24
|
-
def negate = LabelExpression.new(:negation, nil, [self])
|
|
25
|
-
alias not negate
|
|
26
|
-
|
|
27
|
-
def leaf? = type == :leaf
|
|
28
|
-
|
|
29
|
-
private
|
|
30
|
-
|
|
31
|
-
def coerce(other) = other.is_a?(LabelExpression) ? other : LabelExpression.exactly(other)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Factory for label expressions: `Labels.exactly('A').or('B')`.
|
|
35
|
-
module Labels
|
|
36
|
-
module_function
|
|
37
|
-
|
|
38
|
-
def exactly(name) = LabelExpression.exactly(name)
|
|
39
|
-
end
|
|
40
|
-
end
|