neo4j-core 3.0.0.alpha.16 → 3.0.0.alpha.17
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/README.md +14 -37
- data/lib/mydb/index/lucene-store.db +0 -0
- data/lib/mydb/index/lucene.log.active +0 -0
- data/lib/mydb/index/{lucene.log.1 → lucene.log.v0} +0 -0
- data/lib/mydb/messages.log +359 -0
- data/lib/mydb/neostore +0 -0
- data/lib/mydb/neostore.id +0 -0
- data/lib/mydb/neostore.labeltokenstore.db +0 -0
- data/lib/mydb/neostore.labeltokenstore.db.id +0 -0
- data/lib/mydb/neostore.labeltokenstore.db.names +0 -0
- data/lib/mydb/neostore.labeltokenstore.db.names.id +0 -0
- data/lib/mydb/neostore.nodestore.db +0 -0
- data/lib/mydb/neostore.nodestore.db.id +0 -0
- data/lib/mydb/neostore.nodestore.db.labels +0 -0
- data/lib/mydb/neostore.nodestore.db.labels.id +0 -0
- data/lib/mydb/neostore.propertystore.db +0 -0
- data/lib/mydb/neostore.propertystore.db.arrays +0 -0
- data/lib/mydb/neostore.propertystore.db.arrays.id +0 -0
- data/lib/mydb/neostore.propertystore.db.id +0 -0
- data/lib/mydb/neostore.propertystore.db.index +0 -0
- data/lib/mydb/neostore.propertystore.db.index.id +0 -0
- data/lib/mydb/neostore.propertystore.db.index.keys +0 -0
- data/lib/mydb/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/mydb/neostore.propertystore.db.strings +0 -0
- data/lib/mydb/neostore.propertystore.db.strings.id +0 -0
- data/lib/mydb/neostore.relationshipgroupstore.db +0 -0
- data/lib/mydb/neostore.relationshipgroupstore.db.id +0 -0
- data/lib/mydb/neostore.relationshipstore.db +0 -0
- data/lib/mydb/neostore.relationshipstore.db.id +0 -0
- data/lib/mydb/neostore.relationshiptypestore.db +1 -0
- data/lib/mydb/neostore.relationshiptypestore.db.id +0 -0
- data/lib/mydb/neostore.relationshiptypestore.db.names +0 -0
- data/lib/mydb/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/mydb/neostore.schemastore.db +0 -0
- data/lib/mydb/neostore.schemastore.db.id +0 -0
- data/lib/mydb/nioneo_logical.log.active +0 -0
- data/lib/mydb/nioneo_logical.log.v0 +0 -0
- data/lib/mydb/schema/label/lucene/segments.gen +0 -0
- data/lib/mydb/schema/label/lucene/segments_1 +0 -0
- data/lib/mydb/tm_tx_log.1 +0 -0
- data/lib/neo4j-core.rb +1 -1
- data/lib/neo4j-core/helpers.rb +1 -1
- data/lib/neo4j-core/query.rb +298 -0
- data/lib/neo4j-core/query_clauses.rb +476 -0
- data/lib/neo4j-core/version.rb +1 -1
- data/lib/neo4j-core/version.rb~ +5 -0
- data/lib/neo4j-embedded/cypher_response.rb +9 -45
- data/lib/neo4j-embedded/embedded_session.rb +15 -17
- data/lib/neo4j-server/cypher_node.rb +16 -5
- data/lib/neo4j-server/cypher_relationship.rb +24 -6
- data/lib/neo4j-server/cypher_response.rb +35 -35
- data/lib/neo4j-server/cypher_session.rb +8 -12
- data/lib/neo4j/node.rb +4 -4
- data/lib/neo4j/session.rb +11 -63
- data/lib/neo4j/tasks/neo4j_server.rb +31 -0
- metadata +9 -7
- data/lib/mydb/lock +0 -0
- data/lib/mydb/nioneo_logical.log.1 +0 -0
- data/lib/mydb/schema/label/lucene/write.lock +0 -0
- data/lib/neo4j-core/query_builder.rb +0 -200
data/lib/mydb/neostore
CHANGED
Binary file
|
data/lib/mydb/neostore.id
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
RelationshipTypeStore v0.A.3
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
data/lib/mydb/tm_tx_log.1
CHANGED
Binary file
|
data/lib/neo4j-core.rb
CHANGED
@@ -5,7 +5,7 @@ require 'neo4j/property_validator'
|
|
5
5
|
require 'neo4j/property_container'
|
6
6
|
require 'neo4j-core/helpers'
|
7
7
|
require 'neo4j-core/cypher_translator'
|
8
|
-
require 'neo4j-core/
|
8
|
+
require 'neo4j-core/query'
|
9
9
|
|
10
10
|
require 'neo4j/entity_equality'
|
11
11
|
require 'neo4j/node'
|
data/lib/neo4j-core/helpers.rb
CHANGED
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'neo4j-core/query_clauses'
|
2
|
+
|
3
|
+
module Neo4j::Core
|
4
|
+
# Allows for generation of cypher queries via ruby method calls (inspired by ActiveRecord / arel syntax)
|
5
|
+
#
|
6
|
+
# Can be used to express cypher queries in ruby nicely, or to more easily generate queries programatically.
|
7
|
+
#
|
8
|
+
# Also, queries can be passed around an application to progressively build a query across different concerns
|
9
|
+
#
|
10
|
+
# See also the following link for full cypher language documentation:
|
11
|
+
# http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html
|
12
|
+
class Query
|
13
|
+
include Neo4j::Core::QueryClauses
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@session = options[:session] || Neo4j::Session.current
|
17
|
+
|
18
|
+
@options = options
|
19
|
+
@clauses = []
|
20
|
+
@params = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# @method start *args
|
24
|
+
# START clause
|
25
|
+
# @return [Query]
|
26
|
+
|
27
|
+
# @method match *args
|
28
|
+
# MATCH clause
|
29
|
+
# @return [Query]
|
30
|
+
|
31
|
+
# @method optional_match *args
|
32
|
+
# OPTIONAL MATCH clause
|
33
|
+
# @return [Query]
|
34
|
+
|
35
|
+
# @method using *args
|
36
|
+
# USING clause
|
37
|
+
# @return [Query]
|
38
|
+
|
39
|
+
# @method where *args
|
40
|
+
# WHERE clause
|
41
|
+
# @return [Query]
|
42
|
+
|
43
|
+
# @method with *args
|
44
|
+
# WITH clause
|
45
|
+
# @return [Query]
|
46
|
+
|
47
|
+
# @method order *args
|
48
|
+
# ORDER BY clause
|
49
|
+
# @return [Query]
|
50
|
+
|
51
|
+
# @method limit *args
|
52
|
+
# LIMIT clause
|
53
|
+
# @return [Query]
|
54
|
+
|
55
|
+
# @method skip *args
|
56
|
+
# SKIP clause
|
57
|
+
# @return [Query]
|
58
|
+
|
59
|
+
# @method set *args
|
60
|
+
# SET clause
|
61
|
+
# @return [Query]
|
62
|
+
|
63
|
+
# @method remove *args
|
64
|
+
# REMOVE clause
|
65
|
+
# @return [Query]
|
66
|
+
|
67
|
+
# @method unwind *args
|
68
|
+
# UNWIND clause
|
69
|
+
# @return [Query]
|
70
|
+
|
71
|
+
# @method return *args
|
72
|
+
# RETURN clause
|
73
|
+
# @return [Query]
|
74
|
+
|
75
|
+
# @method create *args
|
76
|
+
# CREATE clause
|
77
|
+
# @return [Query]
|
78
|
+
|
79
|
+
# @method create_unique *args
|
80
|
+
# CREATE UNIQUE clause
|
81
|
+
# @return [Query]
|
82
|
+
|
83
|
+
# @method merge *args
|
84
|
+
# MERGE clause
|
85
|
+
# @return [Query]
|
86
|
+
|
87
|
+
# @method on_create_set *args
|
88
|
+
# ON CREATE SET clause
|
89
|
+
# @return [Query]
|
90
|
+
|
91
|
+
# @method on_match_set *args
|
92
|
+
# ON MATCH SET clause
|
93
|
+
# @return [Query]
|
94
|
+
|
95
|
+
# @method delete *args
|
96
|
+
# DELETE clause
|
97
|
+
# @return [Query]
|
98
|
+
|
99
|
+
METHODS = %w[with start match optional_match using where set create create_unique merge on_create_set on_match_set remove unwind delete return order skip limit]
|
100
|
+
|
101
|
+
CLAUSES = METHODS.map {|method| const_get(method.split('_').map {|c| c.capitalize }.join + 'Clause') }
|
102
|
+
|
103
|
+
METHODS.each_with_index do |clause, i|
|
104
|
+
clause_class = CLAUSES[i]
|
105
|
+
|
106
|
+
module_eval(%Q{
|
107
|
+
def #{clause}(*args)
|
108
|
+
build_deeper_query(#{clause_class}, args)
|
109
|
+
end}, __FILE__, __LINE__)
|
110
|
+
end
|
111
|
+
|
112
|
+
alias_method :offset, :skip
|
113
|
+
alias_method :order_by, :order
|
114
|
+
|
115
|
+
# Works the same as the #set method, but when given a nested array it will set properties rather than setting entire objects
|
116
|
+
# @example
|
117
|
+
# # Creates a query representing the cypher: MATCH (n:Person) SET n.age = 19
|
118
|
+
# Query.new.match(n: :Person).set_props(n: {age: 19})
|
119
|
+
def set_props(*args)
|
120
|
+
build_deeper_query(SetClause, args, set_props: true)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Allows what's been built of the query so far to be frozen and the rest built anew. Can be called multiple times in a string of method calls
|
124
|
+
# @example
|
125
|
+
# # Creates a query representing the cypher: MATCH (q:Person), r:Car MATCH (p: Person)-->q
|
126
|
+
# Query.new.match(q: Person).match('r:Car').break.match('(p: Person)-->q')
|
127
|
+
def break
|
128
|
+
build_deeper_query(nil)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Allows for the specification of values for params specified in query
|
132
|
+
# @example
|
133
|
+
# # Creates a query representing the cypher: MATCH (q: Person {id: {id}})
|
134
|
+
# # Calls to params don't affect the cypher query generated, but the params will be
|
135
|
+
# # Passed down when the query is made
|
136
|
+
# Query.new.match('(q: Person {id: {id}})').params(id: 12)
|
137
|
+
#
|
138
|
+
def params(args)
|
139
|
+
@params = @params.merge(args)
|
140
|
+
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def response
|
145
|
+
response = @session._query(self.to_cypher, @params)
|
146
|
+
if !response.respond_to?(:error?) || !response.error?
|
147
|
+
response
|
148
|
+
else
|
149
|
+
response.raise_cypher_error
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
include Enumerable
|
154
|
+
|
155
|
+
def each
|
156
|
+
response = self.response
|
157
|
+
if response.is_a?(Neo4j::Server::CypherResponse)
|
158
|
+
self.response.to_node_enumeration
|
159
|
+
else
|
160
|
+
Neo4j::Embedded::ResultWrapper.new(response, self.to_cypher)
|
161
|
+
end.each {|object| yield object }
|
162
|
+
end
|
163
|
+
|
164
|
+
# @method to_a
|
165
|
+
# Class is Enumerable. Each yield is a Hash with the key matching the variable returned and the value being the value for that key from the response
|
166
|
+
# @return [Array]
|
167
|
+
# @raise [Neo4j::Server::CypherResponse::ResponseError] Raises errors from neo4j server
|
168
|
+
|
169
|
+
|
170
|
+
# Executes a query without returning the result
|
171
|
+
# @return [Boolean] true if successful
|
172
|
+
# @raise [Neo4j::Server::CypherResponse::ResponseError] Raises errors from neo4j server
|
173
|
+
def exec
|
174
|
+
self.response
|
175
|
+
|
176
|
+
true
|
177
|
+
end
|
178
|
+
|
179
|
+
# Return the specified columns as an array.
|
180
|
+
# If one column is specified, a one-dimensional array is returned with the values of that column
|
181
|
+
# If two columns are specified, a n-dimensional array is returned with the values of those columns
|
182
|
+
#
|
183
|
+
# @example
|
184
|
+
# Query.new.match(n: :Person).return(p: :name}.pluck(p: :name) # => Array of names
|
185
|
+
# @example
|
186
|
+
# Query.new.match(n: :Person).return(p: :name}.pluck('p, p.name') # => Array of [node, name] pairs
|
187
|
+
#
|
188
|
+
def pluck(*columns)
|
189
|
+
query = self.dup
|
190
|
+
query.remove_clause_class(ReturnClause)
|
191
|
+
|
192
|
+
columns = columns.map do |column_definition|
|
193
|
+
if column_definition.is_a?(Hash)
|
194
|
+
column_definition.map {|k, v| "#{k}.#{v}" }
|
195
|
+
else
|
196
|
+
column_definition
|
197
|
+
end
|
198
|
+
end.flatten.map(&:to_sym)
|
199
|
+
|
200
|
+
query = query.return(columns)
|
201
|
+
|
202
|
+
case columns.size
|
203
|
+
when 0
|
204
|
+
raise ArgumentError, 'No columns specified for Query#pluck'
|
205
|
+
when 1
|
206
|
+
column = columns[0]
|
207
|
+
query.map {|row| row[column] }
|
208
|
+
else
|
209
|
+
query.map do |row|
|
210
|
+
columns.map do |column|
|
211
|
+
row[column]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
# Returns a CYPHER query string from the object query representation
|
219
|
+
# @example
|
220
|
+
# Query.new.match(p: :Person).where(p: {age: 30}) # => "MATCH (p:Person) WHERE p.age = 30
|
221
|
+
#
|
222
|
+
# @return [String] Resulting cypher query string
|
223
|
+
def to_cypher
|
224
|
+
cypher_string = partitioned_clauses.map do |clauses|
|
225
|
+
clauses_by_class = clauses.group_by(&:class)
|
226
|
+
|
227
|
+
cypher_parts = CLAUSES.map do |clause_class|
|
228
|
+
clauses = clauses_by_class[clause_class]
|
229
|
+
|
230
|
+
clause_class.to_cypher(clauses) if clauses
|
231
|
+
end
|
232
|
+
|
233
|
+
cypher_string = cypher_parts.compact.join(' ')
|
234
|
+
cypher_string.strip
|
235
|
+
end.join ' '
|
236
|
+
|
237
|
+
cypher_string = "CYPHER #{@options[:parser]} #{cypher_string}" if @options[:parser]
|
238
|
+
cypher_string.strip
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns a CYPHER query specifying the union of the callee object's query and the argument's query
|
242
|
+
#
|
243
|
+
# @example
|
244
|
+
# # Generates cypher: MATCH (n:Person) UNION MATCH (o:Person) WHERE o.age = 10
|
245
|
+
# q = Neo4j::Core::Query.new.match(o: :Person).where(o: {age: 10})
|
246
|
+
# result = Neo4j::Core::Query.new.match(n: :Person).union_cypher(q)
|
247
|
+
#
|
248
|
+
# @param other_query [Query] Second half of UNION
|
249
|
+
# @param options [Hash] Specify {all: true} to use UNION ALL
|
250
|
+
# @return [String] Resulting UNION cypher query string
|
251
|
+
def union_cypher(other_query, options = {})
|
252
|
+
"#{self.to_cypher} UNION#{options[:all] ? ' ALL' : ''} #{other_query.to_cypher}"
|
253
|
+
end
|
254
|
+
|
255
|
+
protected
|
256
|
+
|
257
|
+
def add_clauses(clauses)
|
258
|
+
@clauses += clauses
|
259
|
+
end
|
260
|
+
|
261
|
+
def remove_clause_class(clause_class)
|
262
|
+
@clauses = @clauses.reject do |clause|
|
263
|
+
clause.is_a?(clause_class)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
private
|
267
|
+
|
268
|
+
def build_deeper_query(clause_class, args = {}, options = {})
|
269
|
+
self.dup.tap do |new_query|
|
270
|
+
new_query.add_clauses [nil] if [nil, WithClause].include?(clause_class)
|
271
|
+
new_query.add_clauses clause_class.from_args(args, options) if clause_class
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def break_deeper_query
|
276
|
+
self.dup.tap do |new_query|
|
277
|
+
new_query.add_clauses [nil]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def partitioned_clauses
|
282
|
+
partitioning = [[]]
|
283
|
+
|
284
|
+
@clauses.each do |clause|
|
285
|
+
if clause.nil? && partitioning.last != []
|
286
|
+
partitioning << []
|
287
|
+
else
|
288
|
+
partitioning.last << clause
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
partitioning
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
|
@@ -0,0 +1,476 @@
|
|
1
|
+
module Neo4j::Core
|
2
|
+
module QueryClauses
|
3
|
+
|
4
|
+
class ArgError < StandardError
|
5
|
+
attr_reader :arg_part
|
6
|
+
def initialize(arg_part = nil)
|
7
|
+
super
|
8
|
+
@arg_part = arg_part
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
class Clause
|
14
|
+
include CypherTranslator
|
15
|
+
|
16
|
+
def initialize(arg, options = {})
|
17
|
+
@arg = arg
|
18
|
+
@options = options
|
19
|
+
end
|
20
|
+
|
21
|
+
def value
|
22
|
+
if @arg.is_a?(String)
|
23
|
+
self.from_string @arg
|
24
|
+
|
25
|
+
elsif @arg.is_a?(Symbol) && self.respond_to?(:from_symbol)
|
26
|
+
self.from_symbol @arg
|
27
|
+
|
28
|
+
elsif @arg.is_a?(Integer) && self.respond_to?(:from_integer)
|
29
|
+
self.from_integer @arg
|
30
|
+
|
31
|
+
elsif @arg.is_a?(Hash)
|
32
|
+
if self.respond_to?(:from_hash)
|
33
|
+
self.from_hash @arg
|
34
|
+
elsif self.respond_to?(:from_key_and_value)
|
35
|
+
@arg.map do |key, value|
|
36
|
+
self.from_key_and_value key, value
|
37
|
+
end
|
38
|
+
else
|
39
|
+
raise ArgError.new
|
40
|
+
end
|
41
|
+
|
42
|
+
else
|
43
|
+
raise ArgError.new
|
44
|
+
end
|
45
|
+
|
46
|
+
rescue ArgError => arg_error
|
47
|
+
message = "Invalid argument for #{self.class.keyword}. Full arguments: #{@arg.inspect}"
|
48
|
+
message += " | Invalid part: #{arg_error.arg_part.inspect}" if arg_error.arg_part
|
49
|
+
|
50
|
+
raise ArgumentError, message
|
51
|
+
end
|
52
|
+
|
53
|
+
def from_string(value)
|
54
|
+
value
|
55
|
+
end
|
56
|
+
|
57
|
+
def node_from_key_and_value(key, value, options = {})
|
58
|
+
prefer = options[:prefer] || :var
|
59
|
+
|
60
|
+
var, label_string, attributes_string = nil
|
61
|
+
|
62
|
+
case value
|
63
|
+
when String, Symbol
|
64
|
+
var = key
|
65
|
+
label_string = value
|
66
|
+
when Hash
|
67
|
+
if !value.values.any? {|v| v.is_a?(Hash) }
|
68
|
+
case prefer
|
69
|
+
when :var
|
70
|
+
var = key
|
71
|
+
when :label
|
72
|
+
label_string = key
|
73
|
+
end
|
74
|
+
else
|
75
|
+
var = key
|
76
|
+
end
|
77
|
+
|
78
|
+
if value.size == 1 && value.values.first.is_a?(Hash)
|
79
|
+
label_string, attributes = value.first
|
80
|
+
attributes_string = attributes_string(attributes)
|
81
|
+
else
|
82
|
+
attributes_string = attributes_string(value)
|
83
|
+
end
|
84
|
+
when Class
|
85
|
+
var = key
|
86
|
+
label_string = defined?(value::CYPHER_LABEL) ? value::CYPHER_LABEL : value.name
|
87
|
+
else
|
88
|
+
raise ArgError.new(value)
|
89
|
+
end
|
90
|
+
|
91
|
+
"(#{var}#{format_label(label_string)}#{attributes_string})"
|
92
|
+
end
|
93
|
+
|
94
|
+
class << self
|
95
|
+
attr_reader :keyword
|
96
|
+
|
97
|
+
def from_args(args, options = {})
|
98
|
+
args.flatten.map do |arg|
|
99
|
+
if !arg.respond_to?(:empty?) || !arg.empty?
|
100
|
+
self.new(arg, options)
|
101
|
+
end
|
102
|
+
end.compact
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_cypher(clauses)
|
106
|
+
"#{@keyword} #{clause_string(clauses)}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def format_label(label_string)
|
113
|
+
label_string = label_string.to_s.strip
|
114
|
+
if !label_string.empty? && label_string[0] != ':'
|
115
|
+
label_string = "`#{label_string}`" unless label_string.match(' ')
|
116
|
+
label_string = ":#{label_string}"
|
117
|
+
end
|
118
|
+
label_string
|
119
|
+
end
|
120
|
+
|
121
|
+
def attributes_string(attributes)
|
122
|
+
attributes_string = attributes.map do |key, value|
|
123
|
+
v = value.to_s.match(/^{.+}$/) ? value : value.inspect
|
124
|
+
"#{key}: #{v}"
|
125
|
+
end.join(', ')
|
126
|
+
|
127
|
+
" {#{attributes_string}}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class StartClause < Clause
|
132
|
+
@keyword = 'START'
|
133
|
+
|
134
|
+
def from_symbol(value)
|
135
|
+
from_string(value.to_s)
|
136
|
+
end
|
137
|
+
|
138
|
+
def from_key_and_value(key, value)
|
139
|
+
case value
|
140
|
+
when String, Symbol
|
141
|
+
"#{key} = #{value}"
|
142
|
+
else
|
143
|
+
raise ArgError.new(value)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class << self
|
148
|
+
def clause_string(clauses)
|
149
|
+
clauses.map(&:value).join(', ')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class WhereClause < Clause
|
155
|
+
@keyword = 'WHERE'
|
156
|
+
|
157
|
+
def from_key_and_value(key, value)
|
158
|
+
case value
|
159
|
+
when Hash
|
160
|
+
value.map do |k, v|
|
161
|
+
if k.to_sym == :neo_id
|
162
|
+
"ID(#{key}) = #{v}"
|
163
|
+
else
|
164
|
+
key.to_s + '.' + from_key_and_value(k, v)
|
165
|
+
end
|
166
|
+
end.join(' AND ')
|
167
|
+
when Array
|
168
|
+
"#{key} IN [#{value.join(', ')}]"
|
169
|
+
when NilClass
|
170
|
+
"#{key} IS NULL"
|
171
|
+
when Regexp
|
172
|
+
pattern = (value.casefold? ? "(?i)" : "") + value.source
|
173
|
+
"#{key} =~ #{escape_value(pattern.gsub(/\\/, '\\\\\\'))}"
|
174
|
+
else
|
175
|
+
"#{key} = #{value.inspect}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class << self
|
180
|
+
def clause_string(clauses)
|
181
|
+
clauses.map(&:value).join(' AND ')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
class MatchClause < Clause
|
188
|
+
@keyword = 'MATCH'
|
189
|
+
|
190
|
+
def from_symbol(value)
|
191
|
+
from_string(value.to_s)
|
192
|
+
end
|
193
|
+
|
194
|
+
def from_key_and_value(key, value)
|
195
|
+
self.node_from_key_and_value(key, value)
|
196
|
+
end
|
197
|
+
|
198
|
+
class << self
|
199
|
+
def clause_string(clauses)
|
200
|
+
clauses.map(&:value).join(', ')
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
class OptionalMatchClause < MatchClause
|
206
|
+
@keyword = 'OPTIONAL MATCH'
|
207
|
+
end
|
208
|
+
|
209
|
+
class WithClause < Clause
|
210
|
+
@keyword = 'WITH'
|
211
|
+
|
212
|
+
def from_symbol(value)
|
213
|
+
from_string(value.to_s)
|
214
|
+
end
|
215
|
+
|
216
|
+
def from_key_and_value(key, value)
|
217
|
+
"#{value} AS #{key}"
|
218
|
+
end
|
219
|
+
|
220
|
+
class << self
|
221
|
+
def clause_string(clauses)
|
222
|
+
clauses.map(&:value).join(', ')
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
class UsingClause < Clause
|
228
|
+
@keyword = 'USING'
|
229
|
+
|
230
|
+
class << self
|
231
|
+
def clause_string(clauses)
|
232
|
+
clauses.map(&:value).join(" #{@keyword} ")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class CreateClause < Clause
|
238
|
+
@keyword = 'CREATE'
|
239
|
+
|
240
|
+
def from_string(value)
|
241
|
+
value
|
242
|
+
end
|
243
|
+
|
244
|
+
def from_symbol(value)
|
245
|
+
"(:#{value})"
|
246
|
+
end
|
247
|
+
|
248
|
+
def from_hash(hash)
|
249
|
+
if hash.values.any? {|value| value.is_a?(Hash) }
|
250
|
+
hash.map do |key, value|
|
251
|
+
from_key_and_value(key, value)
|
252
|
+
end
|
253
|
+
else
|
254
|
+
"(#{attributes_string(hash)})"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def from_key_and_value(key, value)
|
259
|
+
self.node_from_key_and_value(key, value, prefer: :label)
|
260
|
+
end
|
261
|
+
|
262
|
+
class << self
|
263
|
+
def clause_string(clauses)
|
264
|
+
clauses.map(&:value).join(', ')
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
class CreateUniqueClause < CreateClause
|
270
|
+
@keyword = 'CREATE UNIQUE'
|
271
|
+
end
|
272
|
+
|
273
|
+
class MergeClause < CreateClause
|
274
|
+
@keyword = 'MERGE'
|
275
|
+
end
|
276
|
+
|
277
|
+
class DeleteClause < Clause
|
278
|
+
@keyword = 'DELETE'
|
279
|
+
|
280
|
+
def from_symbol(value)
|
281
|
+
from_string(value.to_s)
|
282
|
+
end
|
283
|
+
|
284
|
+
class << self
|
285
|
+
def clause_string(clauses)
|
286
|
+
clauses.map(&:value).join(', ')
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class OrderClause < Clause
|
292
|
+
@keyword = 'ORDER BY'
|
293
|
+
|
294
|
+
def from_symbol(value)
|
295
|
+
from_string(value.to_s)
|
296
|
+
end
|
297
|
+
|
298
|
+
def from_key_and_value(key, value)
|
299
|
+
case value
|
300
|
+
when String, Symbol
|
301
|
+
"#{key}.#{value}"
|
302
|
+
when Array
|
303
|
+
value.map do |v|
|
304
|
+
if v.is_a?(Hash)
|
305
|
+
from_key_and_value(key, v)
|
306
|
+
else
|
307
|
+
"#{key}.#{v}"
|
308
|
+
end
|
309
|
+
end
|
310
|
+
when Hash
|
311
|
+
value.map do |k, v|
|
312
|
+
"#{key}.#{k} #{v.upcase}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
class << self
|
318
|
+
def clause_string(clauses)
|
319
|
+
clauses.map(&:value).join(', ')
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
class LimitClause < Clause
|
325
|
+
@keyword = 'LIMIT'
|
326
|
+
|
327
|
+
def from_string(value)
|
328
|
+
value.to_i
|
329
|
+
end
|
330
|
+
|
331
|
+
def from_integer(value)
|
332
|
+
value
|
333
|
+
end
|
334
|
+
|
335
|
+
class << self
|
336
|
+
def clause_string(clauses)
|
337
|
+
clauses.last.value
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
class SkipClause < Clause
|
343
|
+
@keyword = 'SKIP'
|
344
|
+
|
345
|
+
def from_string(value)
|
346
|
+
value.to_i
|
347
|
+
end
|
348
|
+
|
349
|
+
def from_integer(value)
|
350
|
+
value
|
351
|
+
end
|
352
|
+
|
353
|
+
class << self
|
354
|
+
def clause_string(clauses)
|
355
|
+
clauses.last.value
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
360
|
+
class SetClause < Clause
|
361
|
+
@keyword = 'SET'
|
362
|
+
|
363
|
+
def from_key_and_value(key, value)
|
364
|
+
case value
|
365
|
+
when String, Symbol
|
366
|
+
"#{key} = #{value}"
|
367
|
+
when Hash
|
368
|
+
if @options[:set_props]
|
369
|
+
value.map do |k, v|
|
370
|
+
"#{key}.#{k} = #{v.inspect}"
|
371
|
+
end
|
372
|
+
else
|
373
|
+
attribute_string = value.map {|k, v| "#{k}: #{v.inspect}" }.join(', ')
|
374
|
+
"#{key} = {#{attribute_string}}"
|
375
|
+
end
|
376
|
+
else
|
377
|
+
raise ArgError.new(value)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
class << self
|
382
|
+
def clause_string(clauses)
|
383
|
+
clauses.map(&:value).join(', ')
|
384
|
+
end
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
class OnCreateSetClause < SetClause
|
389
|
+
@keyword = 'ON CREATE SET'
|
390
|
+
|
391
|
+
def initialize(*args)
|
392
|
+
super
|
393
|
+
@options[:set_props] = true
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
class OnMatchSetClause < OnCreateSetClause
|
398
|
+
@keyword = 'ON MATCH SET'
|
399
|
+
end
|
400
|
+
|
401
|
+
class RemoveClause < Clause
|
402
|
+
@keyword = 'REMOVE'
|
403
|
+
|
404
|
+
def from_key_and_value(key, value)
|
405
|
+
case value
|
406
|
+
when /^:/
|
407
|
+
"#{key}:#{value[1..-1]}"
|
408
|
+
when String
|
409
|
+
"#{key}.#{value}"
|
410
|
+
when Symbol
|
411
|
+
"#{key}:#{value}"
|
412
|
+
else
|
413
|
+
raise ArgError.new(value)
|
414
|
+
end
|
415
|
+
|
416
|
+
end
|
417
|
+
|
418
|
+
class << self
|
419
|
+
def clause_string(clauses)
|
420
|
+
clauses.map(&:value).join(', ')
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
class UnwindClause < Clause
|
426
|
+
@keyword = 'UNWIND'
|
427
|
+
|
428
|
+
def from_key_and_value(key, value)
|
429
|
+
case value
|
430
|
+
when String, Symbol
|
431
|
+
"#{value} AS #{key}"
|
432
|
+
when Array
|
433
|
+
"#{value.inspect} AS #{key}"
|
434
|
+
else
|
435
|
+
raise ArgError.new(value)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
class << self
|
440
|
+
def clause_string(clauses)
|
441
|
+
clauses.map(&:value).join(' UNWIND ')
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
class ReturnClause < Clause
|
447
|
+
@keyword = 'RETURN'
|
448
|
+
|
449
|
+
def from_symbol(value)
|
450
|
+
from_string(value.to_s)
|
451
|
+
end
|
452
|
+
|
453
|
+
def from_key_and_value(key, value)
|
454
|
+
case value
|
455
|
+
when Array
|
456
|
+
value.map do |v|
|
457
|
+
from_key_and_value(key, v)
|
458
|
+
end.join(', ')
|
459
|
+
when String, Symbol
|
460
|
+
"#{key}.#{value}"
|
461
|
+
else
|
462
|
+
raise ArgError.new(value)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
class << self
|
467
|
+
def clause_string(clauses)
|
468
|
+
clauses.map(&:value).join(', ')
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|