neo4j-cypher 1.0.0.rc1
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.
- data/Gemfile +15 -0
- data/README.rdoc +71 -0
- data/lib/neo4j-cypher.rb +47 -0
- data/lib/neo4j-cypher/argument.rb +39 -0
- data/lib/neo4j-cypher/clause.rb +76 -0
- data/lib/neo4j-cypher/clause_list.rb +101 -0
- data/lib/neo4j-cypher/context.rb +411 -0
- data/lib/neo4j-cypher/create.rb +99 -0
- data/lib/neo4j-cypher/match.rb +343 -0
- data/lib/neo4j-cypher/mixins.rb +43 -0
- data/lib/neo4j-cypher/node_var.rb +65 -0
- data/lib/neo4j-cypher/operator.rb +130 -0
- data/lib/neo4j-cypher/predicate.rb +64 -0
- data/lib/neo4j-cypher/property.rb +106 -0
- data/lib/neo4j-cypher/rel_var.rb +128 -0
- data/lib/neo4j-cypher/result.rb +43 -0
- data/lib/neo4j-cypher/result_wrapper.rb +47 -0
- data/lib/neo4j-cypher/return.rb +124 -0
- data/lib/neo4j-cypher/root.rb +182 -0
- data/lib/neo4j-cypher/start.rb +88 -0
- data/lib/neo4j-cypher/version.rb +5 -0
- data/lib/neo4j-cypher/where.rb +16 -0
- data/lib/neo4j-cypher/with.rb +41 -0
- data/neo4j-cypher.gemspec +26 -0
- metadata +81 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
class Delete
|
5
|
+
include Clause
|
6
|
+
|
7
|
+
def initialize(clause_list, var)
|
8
|
+
super(clause_list, :delete)
|
9
|
+
@var = var
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_cypher
|
13
|
+
@var.var_name.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
class Create
|
20
|
+
include ToPropString
|
21
|
+
include Clause
|
22
|
+
include Referenceable
|
23
|
+
|
24
|
+
def initialize(clause_list, props)
|
25
|
+
super(clause_list, :create, EvalContext)
|
26
|
+
@props = props
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_create_path?
|
30
|
+
!!@as_create_path
|
31
|
+
end
|
32
|
+
|
33
|
+
def as_create_path!
|
34
|
+
@as_create_path = true # this is because create path has a little different syntax (extra parantheses)
|
35
|
+
end
|
36
|
+
|
37
|
+
def match_value
|
38
|
+
to_cypher
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_cypher
|
42
|
+
without_parantheses = if @props
|
43
|
+
"#{var_name} #{to_prop_string(@props)}"
|
44
|
+
else
|
45
|
+
var_name
|
46
|
+
end
|
47
|
+
|
48
|
+
as_create_path? ? without_parantheses : "(#{without_parantheses})"
|
49
|
+
end
|
50
|
+
|
51
|
+
class EvalContext
|
52
|
+
include Context
|
53
|
+
include Alias
|
54
|
+
include Variable
|
55
|
+
include Matchable
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class CreatePath
|
61
|
+
include Clause
|
62
|
+
include Referenceable
|
63
|
+
|
64
|
+
attr_reader :arg_list
|
65
|
+
|
66
|
+
def initialize(clause_list, *args, &cypher_dsl)
|
67
|
+
super(clause_list, args.empty? ? :create : :with, EvalContext)
|
68
|
+
|
69
|
+
clause_list.push
|
70
|
+
|
71
|
+
@args = create_clause_args_for(args)
|
72
|
+
@arg_list = @args.map { |a| a.return_value }.join(',')
|
73
|
+
arg_exec = @args.map(&:eval_context)
|
74
|
+
|
75
|
+
RootClause::EvalContext.new(self).instance_exec(*arg_exec, &cypher_dsl)
|
76
|
+
|
77
|
+
@body = "#{clause_list.to_cypher}"
|
78
|
+
clause_list.pop
|
79
|
+
end
|
80
|
+
|
81
|
+
def unique!
|
82
|
+
@unique = true
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_cypher
|
87
|
+
clause_type == :create ? "#{var_name} = #{@body}" : "#{@arg_list} CREATE #{@unique && "UNIQUE "}#{@body}"
|
88
|
+
end
|
89
|
+
|
90
|
+
class EvalContext
|
91
|
+
include Context
|
92
|
+
include Variable
|
93
|
+
include Alias
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,343 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
|
4
|
+
class MatchStart
|
5
|
+
include Clause
|
6
|
+
include Referenceable
|
7
|
+
|
8
|
+
attr_reader :match_list
|
9
|
+
attr_accessor :algorithm
|
10
|
+
|
11
|
+
def initialize(from)
|
12
|
+
super(from.clause_list, :match)
|
13
|
+
@from = from
|
14
|
+
@match_list = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_match_node(from, to, dir)
|
18
|
+
NodeMatchContext.new_first(self, from, to, dir)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_match_rel(rel)
|
23
|
+
RelLeftMatchContext.new(self, @from).set_rel(rel)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def new_match_rels(rels)
|
28
|
+
RelLeftMatchContext.new(self, @from).set_rels(rels)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def new_match_rels?(rels)
|
33
|
+
RelLeftMatchContext.new(self, @from).set_rels?(rels)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def eval_context
|
39
|
+
@match_list.last
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
#negate this match
|
44
|
+
def not
|
45
|
+
clause_list.delete(self)
|
46
|
+
Operator.new(clause_list, self, nil, "not").unary!
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_cypher
|
50
|
+
match_string = @match_list.map(&:to_cypher).join
|
51
|
+
match_string = algorithm ? "#{algorithm}(#{match_string})" : match_string
|
52
|
+
referenced? ? "#{var_name} = #{match_string}" : match_string
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.new_match_node(from, to, dir)
|
56
|
+
MatchStart.new(from).new_match_node(from, to, dir)
|
57
|
+
end
|
58
|
+
|
59
|
+
module MatchContext
|
60
|
+
|
61
|
+
|
62
|
+
## Only in 1.9
|
63
|
+
if RUBY_VERSION > "1.9.0"
|
64
|
+
eval %{
|
65
|
+
def !
|
66
|
+
Operator.new(clause_list, clause, nil, "not").unary!
|
67
|
+
self
|
68
|
+
end }
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(match_start)
|
72
|
+
super(match_start)
|
73
|
+
@match_start = match_start
|
74
|
+
@match_start.match_list << self
|
75
|
+
end
|
76
|
+
|
77
|
+
def convert_create_clauses(to_or_from)
|
78
|
+
# perform a create operation in a match clause ?
|
79
|
+
c = to_or_from.respond_to?(:clause) ? to_or_from.clause : to_or_from
|
80
|
+
if c.respond_to?(:clause_type) && c.clause_type == :create
|
81
|
+
clause_list.delete(c)
|
82
|
+
c.as_create_path!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def clause
|
87
|
+
@match_start
|
88
|
+
end
|
89
|
+
|
90
|
+
def join_previous!
|
91
|
+
@join_previous = true
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def join_previous?
|
96
|
+
@join_previous
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_cypher
|
100
|
+
if join_previous?
|
101
|
+
to_cypher_join
|
102
|
+
else
|
103
|
+
to_cypher_no_join
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Generates a <tt>x in nodes(m3)</tt> cypher expression.
|
108
|
+
#
|
109
|
+
# @example
|
110
|
+
# p.nodes.all? { |x| x[:age] > 30 }
|
111
|
+
def nodes
|
112
|
+
Entities.new(clause.clause_list, "nodes", self).eval_context
|
113
|
+
end
|
114
|
+
|
115
|
+
# Generates a <tt>x in relationships(m3)</tt> cypher expression.
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# p.relationships.all? { |x| x[:age] > 30 }
|
119
|
+
def rels
|
120
|
+
Entities.new(clause.clause_list, "relationships", self).eval_context
|
121
|
+
end
|
122
|
+
|
123
|
+
# returns the length of the path
|
124
|
+
def length
|
125
|
+
clause.referenced!
|
126
|
+
Property.new(clause, 'length').to_function!
|
127
|
+
end
|
128
|
+
|
129
|
+
def not
|
130
|
+
clause.not
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
module JoinableMatchContext
|
135
|
+
|
136
|
+
def next_new_node(to, dir)
|
137
|
+
to_var = NodeVar.as_var(@match_start.clause_list, to)
|
138
|
+
NodeMatchContext.new(@match_start, self, to_var, dir).join_previous!
|
139
|
+
end
|
140
|
+
|
141
|
+
def next_new_rel(rel)
|
142
|
+
RelLeftMatchContext.new(@match_start, self).set_rel(rel).join_previous!
|
143
|
+
end
|
144
|
+
|
145
|
+
def <=>(other)
|
146
|
+
next_new_node(other, :both)
|
147
|
+
end
|
148
|
+
|
149
|
+
def >>(other)
|
150
|
+
next_new_node(other, :outgoing)
|
151
|
+
end
|
152
|
+
|
153
|
+
def <<(other)
|
154
|
+
next_new_node(other, :incoming)
|
155
|
+
end
|
156
|
+
|
157
|
+
def <(rel)
|
158
|
+
next_new_rel(rel)
|
159
|
+
end
|
160
|
+
|
161
|
+
def >(rel)
|
162
|
+
next_new_rel(rel)
|
163
|
+
end
|
164
|
+
|
165
|
+
def -(rel)
|
166
|
+
next_new_rel(rel)
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
module Algorithms
|
172
|
+
|
173
|
+
def shortest_path
|
174
|
+
@match_start.algorithm = "shortestPath"
|
175
|
+
@match_start.eval_context
|
176
|
+
end
|
177
|
+
|
178
|
+
def shortest_paths
|
179
|
+
@match_start.algorithm = "allShortestPaths"
|
180
|
+
@match_start.eval_context
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class RelLeftMatchContext
|
185
|
+
include Context
|
186
|
+
include MatchContext
|
187
|
+
|
188
|
+
def initialize(match_start, from)
|
189
|
+
super(match_start)
|
190
|
+
@from = from
|
191
|
+
convert_create_clauses(from)
|
192
|
+
end
|
193
|
+
|
194
|
+
def set_rels(rels)
|
195
|
+
if rels.size == 1
|
196
|
+
set_rel(rels.first)
|
197
|
+
else
|
198
|
+
# wrap and maybe join several relationship strings
|
199
|
+
@rel_var = RelVar.join(clause_list, rels)
|
200
|
+
end
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
def set_rels?(rels)
|
205
|
+
set_rels(rels)
|
206
|
+
@rel_var.optionally!
|
207
|
+
self
|
208
|
+
end
|
209
|
+
|
210
|
+
def set_rel(rel)
|
211
|
+
return set_rels(rel) if rel.is_a?(Array)
|
212
|
+
|
213
|
+
if rel.is_a?(Neo4j::Cypher::RelVar::EvalContext)
|
214
|
+
@rel_var = rel.clause
|
215
|
+
elsif rel.respond_to?(:clause) && rel.clause.match_value
|
216
|
+
@rel_var = rel.clause
|
217
|
+
else
|
218
|
+
@rel_var = RelVar.new(clause_list, rel)
|
219
|
+
end
|
220
|
+
self
|
221
|
+
end
|
222
|
+
|
223
|
+
def self.new_first(match_start, from, rel)
|
224
|
+
from_var = NodeVar.as_var(match_start.clause_list, from)
|
225
|
+
RelLeftMatchContext.new(match_start, from_var).set_rel(rel)
|
226
|
+
end
|
227
|
+
|
228
|
+
def -(to)
|
229
|
+
@match_start.match_list.delete(self) # since it is complete now
|
230
|
+
RelRightMatchContext.new(@match_start, self, @rel_var, to, :both)
|
231
|
+
end
|
232
|
+
|
233
|
+
def >(to)
|
234
|
+
@match_start.match_list.delete(self)
|
235
|
+
RelRightMatchContext.new(@match_start, self, @rel_var, to, :outgoing)
|
236
|
+
end
|
237
|
+
|
238
|
+
def <(to)
|
239
|
+
@match_start.match_list.delete(self)
|
240
|
+
RelRightMatchContext.new(@match_start, self, @rel_var, to, :incoming)
|
241
|
+
end
|
242
|
+
|
243
|
+
def match_value
|
244
|
+
@from.match_value
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
class RelRightMatchContext
|
249
|
+
include Context
|
250
|
+
include Variable
|
251
|
+
include Returnable
|
252
|
+
include MatchContext
|
253
|
+
include JoinableMatchContext
|
254
|
+
include Algorithms
|
255
|
+
include PredicateMethods
|
256
|
+
include Alias
|
257
|
+
|
258
|
+
FIRST_DIR_OP = {:outgoing => "-", :incoming => "<-", :both => '-'}
|
259
|
+
SECOND_DIR_OP = {:outgoing => "->", :incoming => "-", :both => '-'}
|
260
|
+
|
261
|
+
def initialize(match_start, from, rel_var, to, dir)
|
262
|
+
super(match_start)
|
263
|
+
@from = from
|
264
|
+
convert_create_clauses(from)
|
265
|
+
convert_create_clauses(to)
|
266
|
+
join_previous! if @from.kind_of?(MatchContext) && @from.join_previous?
|
267
|
+
@rel_var = rel_var
|
268
|
+
@dir = dir
|
269
|
+
convert_create_clauses(to)
|
270
|
+
@to = NodeVar.as_var(match_start.clause_list, to)
|
271
|
+
end
|
272
|
+
|
273
|
+
def to_cypher_no_join
|
274
|
+
"(#{@from.match_value})#{FIRST_DIR_OP[@dir]}[#{@rel_var.match_value}]#{SECOND_DIR_OP[@dir]}(#{@to.match_value})"
|
275
|
+
end
|
276
|
+
|
277
|
+
def to_cypher_join
|
278
|
+
"#{FIRST_DIR_OP[@dir]}[#{@rel_var.match_value}]#{SECOND_DIR_OP[@dir]}(#{@to.match_value})"
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
|
284
|
+
class NodeMatchContext
|
285
|
+
include Context
|
286
|
+
include Variable
|
287
|
+
include Returnable
|
288
|
+
include MatchContext
|
289
|
+
include JoinableMatchContext
|
290
|
+
include Alias
|
291
|
+
|
292
|
+
DIR_OPERATORS = {:outgoing => "-->", :incoming => "<--", :both => '--'}
|
293
|
+
|
294
|
+
def initialize(match_start, from, to, dir)
|
295
|
+
super(match_start)
|
296
|
+
@from = from
|
297
|
+
@to = to
|
298
|
+
convert_create_clauses(from)
|
299
|
+
convert_create_clauses(to)
|
300
|
+
@dir = dir
|
301
|
+
end
|
302
|
+
|
303
|
+
|
304
|
+
def self.new_first(match_start, from, to, dir)
|
305
|
+
from_var = NodeVar.as_var(match_start.clause_list, from)
|
306
|
+
to_var = NodeVar.as_var(match_start.clause_list, to)
|
307
|
+
NodeMatchContext.new(match_start, from_var, to_var, dir)
|
308
|
+
end
|
309
|
+
|
310
|
+
def to_cypher_no_join
|
311
|
+
"(#{@from.var_name})#{DIR_OPERATORS[@dir]}(#{@to.var_name})"
|
312
|
+
end
|
313
|
+
|
314
|
+
def to_cypher_join
|
315
|
+
"#{DIR_OPERATORS[@dir]}(#{@to.var_name})"
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
class Entities
|
320
|
+
include Clause
|
321
|
+
|
322
|
+
def initialize(clause_list, iterable, input)
|
323
|
+
super(clause_list, :entities, EvalContext)
|
324
|
+
eval_context.iterable = iterable
|
325
|
+
eval_context.input = input.clause
|
326
|
+
end
|
327
|
+
|
328
|
+
class EvalContext
|
329
|
+
include Context
|
330
|
+
include PredicateMethods
|
331
|
+
attr_accessor :input, :iterable
|
332
|
+
#
|
333
|
+
#def each(&cypher_dsl)
|
334
|
+
# Predicate.new(clause_list, :clause => clause, :input => input, :predicate_block => cypher_dsl)
|
335
|
+
# #RootClause::EvalContext.new(self).instance_exec(*arg_exec, &cypher_dsl)
|
336
|
+
#end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Cypher
|
3
|
+
module Referenceable
|
4
|
+
def var_name
|
5
|
+
@var_name ||= @clause_list.create_variable(self)
|
6
|
+
end
|
7
|
+
|
8
|
+
def var_name=(new_name)
|
9
|
+
@var_name = new_name.to_sym
|
10
|
+
end
|
11
|
+
|
12
|
+
def referenced?
|
13
|
+
!!@referenced
|
14
|
+
end
|
15
|
+
|
16
|
+
def referenced!
|
17
|
+
@referenced = true
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_alias(new_name)
|
21
|
+
@alias = true
|
22
|
+
self.var_name = new_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def as_alias?
|
26
|
+
!!@alias && var_name != return_value
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module ToPropString
|
32
|
+
def to_prop_string(props)
|
33
|
+
key_values = props.keys.map do |key|
|
34
|
+
raw = key.to_s[0, 1] == '_'
|
35
|
+
val = props[key].is_a?(String) && !raw ? "'#{props[key]}'" : props[key]
|
36
|
+
"#{raw ? key.to_s[1..-1] : key} : #{val}"
|
37
|
+
end
|
38
|
+
"{#{key_values.join(', ')}}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|