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 ADDED
@@ -0,0 +1,15 @@
1
+ source :gemcutter
2
+
3
+ gemspec
4
+
5
+ group 'development' do
6
+ gem 'pry'
7
+ gem 'simplecov'
8
+ end
9
+
10
+ group 'test' do
11
+ gem "rake", ">= 0.8.7"
12
+ gem "rspec"
13
+ gem "its" # its(:with, :arguments) { should be_possible }
14
+ end
15
+
data/README.rdoc ADDED
@@ -0,0 +1,71 @@
1
+ == neo4j-cypher {<img src="https://secure.travis-ci.org/andreasronge/neo4j-cypher.png" />}[http://travis-ci.org/andreasronge/neo4j-cypher]
2
+
3
+ A Ruby DSL for the Neo4j Cypher query language for both MRI and JRuby.
4
+ The JRuby neo4j-core gem's cypher dsl has been moved to this gem.
5
+
6
+ === Docs
7
+
8
+ See {Neo4j Wiki Cypher}[https://github.com/andreasronge/neo4j/wiki/Neo4j::Core-Cypher]
9
+
10
+ See the rspecs ! (> 99% test coverage)
11
+
12
+
13
+ === Random Examples
14
+
15
+ Notice the last value returned is the return value (if possible).
16
+ Matching relationships can be done with operators: <,>, -, and <=> or with the both, incoming and outgoing methods.
17
+
18
+ "START v1=node(3) MATCH v2 = (v1)-[:`r`]->(x) RETURN v2"
19
+
20
+ Neo4j::Cypher.query do
21
+ node(3) > :r > :x
22
+ end
23
+
24
+
25
+ "START v2=node(1) MATCH (v2)-[v1:`knows`]->(other) WHERE v1.since > 1994 and other.name = "foo" RETURN other"
26
+
27
+ Neo4j::Cypher.query do
28
+ node(1) > (rel(:knows)[:since] > 1994) > (node(:other)[:name] == 'foo'); :other
29
+ end
30
+
31
+
32
+ "START v2=node(1) MATCH (v2)-[v1:`friends`]->(v3) WHERE (v1.since = 1994) RETURN v3"
33
+
34
+ Neo4j::Cypher.query do
35
+ node(1).outgoing(rel(:friends).where{|r| r[:since] == 1994})
36
+ end
37
+
38
+
39
+ "START v1=node(2,3,4,1) RETURN count(v1.property?"
40
+
41
+ Neo4j::Cypher.query do
42
+ node(2, 3, 4, 1)[:property?].count
43
+ end
44
+
45
+ "START v1=node(42) MATCH (v1)-[:`favorite`]->(stuff)<-[:`favorite`]-(person) WHERE not((v1)-[:`friend`]-(person)) RETURN person.name,count(stuff) ORDER BY count(stuff) DESC"
46
+
47
+ Neo4j::Cypher.query do
48
+ node(42).where_not { |m| m - :friend - :person } > :favorite > :stuff < :favorite < :person
49
+ ret(node(:person)[:name], count(:stuff).desc)
50
+ end
51
+
52
+
53
+ == Complex Example
54
+
55
+ "START n=node(42) MATCH (n)-[r]->(m) WITH n,collect(type(r)) as out_types,collect(m) as outgoing MATCH (n)<-[r]-(m) RETURN n,outgoing,out_types,collect(m) as incoming,collect(type(r)) as in_types"
56
+
57
+ Neo4j::Cypher.query do
58
+ n = node(42).as(:n)
59
+ r = rel('r')
60
+ m = node(:m)
61
+ rel_types = r.rel_type.collect
62
+ end_nodes = m.collect
63
+
64
+ n.with_match(rel_types.as(:out_types), end_nodes.as(:outgoing)) { |n, _, _| n < r < m } > r > m
65
+
66
+ ret([n,
67
+ :outgoing,
68
+ :out_types,
69
+ end_nodes.as(:incoming),
70
+ rel_types.as(:in_types)])
71
+ end
@@ -0,0 +1,47 @@
1
+ require 'neo4j-cypher/version'
2
+
3
+ require 'neo4j-cypher/context'
4
+ require 'neo4j-cypher/mixins'
5
+ require 'neo4j-cypher/clause'
6
+ require 'neo4j-cypher/clause_list'
7
+ require 'neo4j-cypher/argument'
8
+ require 'neo4j-cypher/root'
9
+ require 'neo4j-cypher/start'
10
+ require 'neo4j-cypher/create'
11
+ require 'neo4j-cypher/match'
12
+ require 'neo4j-cypher/return'
13
+ require 'neo4j-cypher/node_var'
14
+ require 'neo4j-cypher/rel_var'
15
+ require 'neo4j-cypher/property'
16
+ require 'neo4j-cypher/predicate'
17
+ require 'neo4j-cypher/with'
18
+ require 'neo4j-cypher/operator'
19
+ require 'neo4j-cypher/where'
20
+ require 'neo4j-cypher/result_wrapper'
21
+ require 'neo4j-cypher/result'
22
+
23
+ module Neo4j
24
+ module Cypher
25
+
26
+ # Creates a Cypher DSL query.
27
+ # To create a new cypher query you must initialize it either an String or a Block.
28
+ #
29
+ # @example <tt>START n0=node(3) MATCH (n0)--(x) RETURN x</tt> same as
30
+ # Cypher.query { start n = node(3); match n <=> :x; ret :x }.to_s
31
+ #
32
+ # @example <tt>START n0=node(3) MATCH (n0)-[:`r`]->(x) RETURN r</tt> same as
33
+ # Cypher.query { node(3) > :r > :x; :r }
34
+ #
35
+ # @example <tt>START n0=node(3) MATCH (n0)-->(x) RETURN x</tt> same as
36
+ # Cypher.query { node(3) >> :x; :x }
37
+ #
38
+ # @param args the argument for the dsl_block
39
+ # @yield the block which will be evaluated in the context of this object in order to create an Cypher Query string
40
+ # @yieldreturn [Return, Object] If the return is not an instance of Return it will be converted it to a Return object (if possible).
41
+ # @return [Cypher::Result]
42
+ def self.query(*args, &dsl_block)
43
+ Result.new(*args, &dsl_block)
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ module Neo4j
2
+ module Cypher
3
+
4
+ class Argument
5
+ include Referenceable
6
+ include Clause
7
+
8
+ def initialize(clause_list, expr, var_name)
9
+ super(clause_list, :argument, EvalContext)
10
+ var_name ||= self.var_name
11
+ @expr = var_name
12
+ as_alias(var_name)
13
+ @return_value = (expr != var_name.to_s) ? "#{expr} as #{var_name}" : expr
14
+ end
15
+
16
+ def return_value
17
+ @return_value
18
+ end
19
+
20
+ def self.new_arg_from_clause(clause)
21
+ Argument.new(clause.clause_list, clause.return_value, clause.as_alias? && clause.var_name)
22
+ end
23
+
24
+ def self.new_arg_from_string(string, clause_list)
25
+ Argument.new(clause_list, string.to_s, string)
26
+ end
27
+
28
+ class EvalContext
29
+ include Context
30
+ include Comparable
31
+ include MathOperator
32
+ include MathFunctions
33
+ include PredicateMethods
34
+ include Aggregate
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,76 @@
1
+ module Neo4j
2
+ module Cypher
3
+
4
+ # Responsible for order of the clauses
5
+ # Does expect a #clause method when included
6
+ module Clause
7
+
8
+ ORDER = [:start, :match, :create, :where, :with, :foreach, :set, :delete, :return, :order_by, :skip, :limit]
9
+ NAME = {:start => 'START', :create => 'CREATE', :match => 'MATCH', :where => "WHERE", :with => 'WITH',
10
+ :return => 'RETURN', :order_by => 'ORDER BY', :skip => 'SKIP', :limit => 'LIMIT', :set => 'SET',
11
+ :delete => 'DELETE', :foreach => 'FOREACH'}
12
+
13
+ attr_accessor :clause_type, :clause_list, :eval_context, :expr, :insert_order
14
+
15
+ def initialize(clause_list, clause_type, eval_context = Context::Empty)
16
+ @clause_type = clause_type
17
+ @clause_list = clause_list
18
+ if eval_context.is_a?(Class)
19
+ @eval_context = eval_context.new(self)
20
+ else
21
+ @eval_context = eval_context
22
+ end
23
+ self.insert_order = 0
24
+ clause_list.insert(self)
25
+ end
26
+
27
+ def <=>(other)
28
+ clause_position == other.clause_position ? insert_order <=> other.insert_order : clause_position <=> other.clause_position
29
+ end
30
+
31
+ def clause_position
32
+ valid_clause?
33
+ ORDER.find_index(clause_type)
34
+ end
35
+
36
+ def valid_clause?
37
+ raise "Unknown clause_type '#{clause_type}' on #{self}" unless ORDER.include?(clause_type)
38
+ end
39
+
40
+ def separator
41
+ ','
42
+ end
43
+
44
+ def match_value=(mv)
45
+ @match_value = mv
46
+ end
47
+
48
+ def match_value
49
+ @match_value || expr || var_name
50
+ end
51
+
52
+ # Used in return clause to generate the last part of the return cypher clause string
53
+ def return_value
54
+ var_name
55
+ end
56
+
57
+ def prefix
58
+ NAME[clause_type]
59
+ end
60
+
61
+ def create_clause_args_for(args)
62
+ args.map do |arg|
63
+ case arg
64
+ when Neo4j::Cypher::ReturnItem::EvalContext, Neo4j::Cypher::Property::EvalContext
65
+ Argument.new_arg_from_clause(arg.clause)
66
+ when String, Symbol
67
+ Argument.new_arg_from_string(arg, clause_list)
68
+ else
69
+ arg.clause
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,101 @@
1
+ module Neo4j
2
+ module Cypher
3
+
4
+ class ClauseList
5
+ attr_accessor :variables
6
+ include Enumerable
7
+
8
+ def initialize(variables = [])
9
+ @variables = variables
10
+ @clause_list = []
11
+ @insert_order = 0
12
+ end
13
+
14
+ def empty?
15
+ !first
16
+ end
17
+
18
+ def include?(clause_type)
19
+ @clause_list.find { |c| c.clause_type == clause_type }
20
+ end
21
+
22
+ def each
23
+ @clause_list.each { |c| yield c }
24
+ end
25
+
26
+ def push
27
+ raise "Only support stack of depth 2" if @old_clause_list
28
+ @old_clause_list = @clause_list
29
+ @clause_list = []
30
+ self
31
+ end
32
+
33
+ def pop
34
+ @clause_list = @old_clause_list
35
+ @clause_list.sort!
36
+ @old_clause_list = nil
37
+ self
38
+ end
39
+
40
+ def insert(clause)
41
+ ctype = clause.clause_type
42
+
43
+ if Clause::ORDER.include?(ctype)
44
+ # which list should we add the cluase to, the root or the sub list ?
45
+ # ALl the start and return clauses should move to the clause_list
46
+ c = (@old_clause_list && (ctype == :start || ctype == :return)) ? @old_clause_list : @clause_list
47
+ c << clause
48
+ @insert_order += 1
49
+ clause.insert_order = @insert_order
50
+ c.sort!
51
+ end
52
+ self
53
+ end
54
+
55
+ def last
56
+ @clause_list.last
57
+ end
58
+
59
+ def delete(clause_or_context)
60
+ c = clause_or_context.respond_to?(:clause) ? clause_or_context.clause : clause_or_context
61
+ @clause_list.delete(c)
62
+ end
63
+
64
+ #def debug
65
+ # puts "ClauseList id: #{object_id}, vars: #{variables.size}"
66
+ # @clause_list.each_with_index { |c, i| puts " #{i} #{c.clause_type.inspect}, #{c.class} id: #{c.object_id} order #{c.insert_order}" }
67
+ #end
68
+
69
+ def create_variable(var)
70
+ raise "Already included #{var}" if @variables.include?(var)
71
+ @variables << var
72
+ "v#{@variables.size}".to_sym
73
+ end
74
+
75
+ def group_by_clause
76
+ prev_clause = nil
77
+ inject([]) do |memo, clause|
78
+ memo << [] if clause.clause_type != prev_clause
79
+ prev_clause = clause.clause_type
80
+ memo.last << clause
81
+ memo
82
+ end
83
+ end
84
+
85
+ def join_group(list)
86
+ list.map { |c| c.to_cypher }.join(list.first.separator)
87
+ end
88
+
89
+ def to_cypher
90
+ # Sub lists, like in with clause should not have a clause prefix like WHERE or MATCH
91
+ group_by_clause.map { |list| "#{prefix(list)}#{join_group(list)}" }.join(' ')
92
+ end
93
+
94
+ def prefix(list)
95
+ @old_clause_list && ![:set, :delete, :create].include?(list.first.clause_type) ? '' : "#{list.first.prefix} "
96
+ end
97
+
98
+ end
99
+ end
100
+
101
+ end
@@ -0,0 +1,411 @@
1
+ module Neo4j
2
+ module Cypher
3
+ module Context
4
+ # @return [Neo4j::Cypher:Clause]
5
+ attr_accessor :clause
6
+
7
+ # @param [Neo4j::Cypher:Clause] clause the clause for this eval context
8
+ def initialize(clause)
9
+ @clause = clause
10
+
11
+ end
12
+
13
+ # @return [Array<Neo4j::Cypher:Clause>] the sorted clause list
14
+ def clause_list
15
+ @clause.clause_list
16
+ end
17
+
18
+ # Used for eval context for a clause which does not allow any more method chaining.
19
+ class Empty
20
+ include Context
21
+ end
22
+
23
+ module Alias
24
+ # Typically used in a WITH statement for a count.as(:stuff) or node(42).as(:foo)
25
+ def as(name)
26
+ clause.as_alias(name)
27
+ self
28
+ end
29
+ end
30
+
31
+ module MathFunctions
32
+ def abs(value=nil)
33
+ _add_math_func(:abs, value)
34
+ end
35
+
36
+ def sqrt(value=nil)
37
+ _add_math_func(:sqrt, value)
38
+ end
39
+
40
+ def round(value=nil)
41
+ _add_math_func(:round, value)
42
+ end
43
+
44
+ def sign(value=nil)
45
+ _add_math_func(:sign, value)
46
+ end
47
+
48
+ # @private
49
+ def _add_math_func(name, value)
50
+ value ||= clause.to_cypher
51
+ clause_list.delete(clause)
52
+ ReturnItem.new(clause_list, "#{name}(#{value})").eval_context
53
+ end
54
+ end
55
+
56
+ module MathOperator
57
+ def -(other)
58
+ Operator.new(clause_list, clause, other, '-').eval_context
59
+ end
60
+
61
+ def +(other)
62
+ Operator.new(clause_list, clause, other, '+').eval_context
63
+ end
64
+ end
65
+
66
+ module Comparable
67
+ def <(other)
68
+ Operator.new(clause_list, clause, other, '<').eval_context
69
+ end
70
+
71
+ def <=(other)
72
+ Operator.new(clause_list, clause, other, '<=').eval_context
73
+ end
74
+
75
+ def =~(other)
76
+ Operator.new(clause_list, clause, other, '=~').eval_context
77
+ end
78
+
79
+ def >(other)
80
+ Operator.new(clause_list, clause, other, '>').eval_context
81
+ end
82
+
83
+ def >=(other)
84
+ Operator.new(clause_list, clause, other, '>=').eval_context
85
+ end
86
+
87
+ ## Only in 1.9
88
+ if RUBY_VERSION > "1.9.0"
89
+ eval %{
90
+ def !=(other)
91
+ Operator.new(clause_list, clause, other, "<>").eval_context
92
+ end }
93
+ end
94
+
95
+ def ==(other)
96
+ Operator.new(clause_list, clause, other, "=").eval_context
97
+ end
98
+ end
99
+
100
+ module PredicateMethods
101
+ def all?(&block)
102
+ self.respond_to?(:iterable)
103
+ Predicate.new(clause_list, :op => 'all', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
104
+ end
105
+
106
+ def extract(&block)
107
+ Predicate.new(clause_list, :op => 'extract', :clause => :return_item, :input => input, :iterable => iterable, :predicate_block => block).eval_context
108
+ end
109
+
110
+ def filter(&block)
111
+ Predicate.new(clause_list, :op => 'filter', :clause => :return_item, :input => input, :iterable => iterable, :predicate_block => block).eval_context
112
+ end
113
+
114
+ def any?(&block)
115
+ Predicate.new(clause_list, :op => 'any', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
116
+ end
117
+
118
+ def none?(&block)
119
+ Predicate.new(clause_list, :op => 'none', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
120
+ end
121
+
122
+ def single?(&block)
123
+ Predicate.new(clause_list, :op => 'single', :clause => :where, :input => input, :iterable => iterable, :predicate_block => block).eval_context
124
+ end
125
+
126
+ def foreach(&block)
127
+ Predicate.new(clause_list, :op => '', :clause => :foreach, :input => input, :iterable => iterable, :predicate_block => block, :separator => ' FOREACH ').eval_context
128
+ input.eval_context
129
+ end
130
+ end
131
+
132
+ module Returnable
133
+ # Specifies a return statement.
134
+ # Notice that this is not needed, since the last value of the DSL block will be converted into one or more
135
+ # return statements.
136
+ # @param [Symbol, #var_name] returns a list of variables we want to return
137
+ # @return [ReturnItem]
138
+ def ret(*returns, &block)
139
+ options = returns.last.is_a?(Hash) ? returns.pop : {}
140
+ returns = [self] if returns.empty? # return self unless not specified what to return
141
+ returns = [RootClause::EvalContext.new(self).instance_exec(self, &block)].flatten if block
142
+ r = Return.new(clause_list, returns, options, &block).eval_context
143
+ (self.is_a?(RootClause::EvalContext)) ? r : self
144
+ end
145
+
146
+ end
147
+
148
+ module Sortable
149
+ def _sort_args(prop)
150
+ return self if prop.nil?
151
+ prop.is_a?(Symbol) ? Property.new(clause, prop).eval_context : prop
152
+ end
153
+
154
+ def asc(*props)
155
+ @return_item ||= ReturnItem.new(clause_list, self).eval_context
156
+ @return_item.asc(_sort_args(props.first))
157
+ end
158
+
159
+ def desc(*props)
160
+ @return_item ||= ReturnItem.new(clause_list, self).eval_context
161
+ @return_item.desc(_sort_args(props.first))
162
+ end
163
+
164
+ end
165
+
166
+ module ReturnOrder
167
+ def _sort_args(props)
168
+ return [self] if props.empty?
169
+ props.map { |p| p.is_a?(Symbol) ? Property.new(clause, p).eval_context : p }
170
+ end
171
+
172
+ # Specifies an <tt>ORDER BY</tt> cypher query
173
+ # @param [Property] props the properties which should be sorted
174
+ # @return self
175
+ def asc(*props)
176
+ @order_by ||= OrderBy.new(clause_list, self)
177
+ clause_list.delete(props.first)
178
+ @order_by.asc(_sort_args(props))
179
+ self
180
+ end
181
+
182
+ # Specifies an <tt>ORDER BY</tt> cypher query
183
+ # @param [Property] props the properties which should be sorted
184
+ # @return self
185
+ def desc(*props)
186
+ @order_by ||= OrderBy.new(clause_list, self)
187
+ clause_list.delete(props.first)
188
+ @order_by.desc(_sort_args(props))
189
+ self
190
+ end
191
+
192
+ # Creates a <tt>SKIP</tt> cypher clause
193
+ # @param [Fixnum] val the number of entries to skip
194
+ # @return self
195
+ def skip(val)
196
+ Skip.new(clause_list, val, self)
197
+ self
198
+ end
199
+
200
+ # Creates a <tt>LIMIT</tt> cypher clause
201
+ # @param [Fixnum] val the number of entries to limit
202
+ # @return self
203
+ def limit(val)
204
+ Limit.new(clause_list, val, self)
205
+ self
206
+ end
207
+
208
+ end
209
+
210
+ module Aggregate
211
+ def distinct
212
+ ReturnItem.new(clause_list, "distinct(#{clause.return_value})").eval_context
213
+ end
214
+
215
+ def count
216
+ ReturnItem.new(clause_list, "count(#{clause.return_value})").eval_context
217
+ end
218
+
219
+ def sum
220
+ ReturnItem.new(clause_list, "sum(#{clause.return_value})").eval_context
221
+ end
222
+
223
+ def avg
224
+ ReturnItem.new(clause_list, "avg(#{clause.return_value})").eval_context
225
+ end
226
+
227
+ def min
228
+ ReturnItem.new(clause_list, "min(#{clause.return_value})").eval_context
229
+ end
230
+
231
+ def max
232
+ ReturnItem.new(clause_list, "max(#{clause.return_value})").eval_context
233
+ end
234
+
235
+ def collect
236
+ ReturnItem.new(clause_list, "collect(#{clause.return_value})").eval_context
237
+ end
238
+
239
+
240
+ def last
241
+ ReturnItem.new(clause_list, "last(#{clause.return_value})").eval_context
242
+ end
243
+
244
+ def tail
245
+ ReturnItem.new(clause_list, "tail(#{clause.return_value})").eval_context
246
+ end
247
+
248
+ def head
249
+ ReturnItem.new(clause_list, "head(#{clause.return_value})").eval_context
250
+ end
251
+
252
+ end
253
+
254
+ module Variable
255
+ def where(&block)
256
+ x = block.call(self)
257
+ clause_list.delete(x)
258
+ Operator.new(clause_list, x.clause, nil, "").unary!
259
+ self
260
+ end
261
+
262
+ def where_not(&block)
263
+ x = block.call(self)
264
+ clause_list.delete(x)
265
+ Operator.new(clause_list, x.clause, nil, "not").unary!
266
+ self
267
+ end
268
+
269
+ def [](prop_name)
270
+ Property.new(clause, prop_name).eval_context
271
+ end
272
+
273
+ # generates a <tt>ID</tt> cypher fragment.
274
+ def neo_id
275
+ Property.new(clause, 'ID').to_function!
276
+ end
277
+
278
+ # generates a <tt>has</tt> cypher fragment.
279
+ def property?(p)
280
+ p = Property.new(clause, p)
281
+ Operator.new(clause_list, p, nil, "has").unary!
282
+ end
283
+
284
+ def []=(p, value)
285
+ left = Property.new(clause, p).eval_context
286
+ Operator.new(clause_list, left, value, "=", :set)
287
+ self
288
+ end
289
+
290
+ def del
291
+ Delete.new(clause_list, clause)
292
+ self
293
+ end
294
+
295
+ # Can be used instead of [_classname] == klass
296
+ def is_a?(klass)
297
+ return super if klass.class != Class || !klass.respond_to?(:_load_wrapper)
298
+ self[:_classname] == klass.to_s
299
+ end
300
+ end
301
+
302
+ module Matchable
303
+
304
+ def with(*args, &cypher_dsl)
305
+ With.new(clause_list, :where, self, *args, &cypher_dsl)
306
+ self
307
+ end
308
+
309
+ def with_match(*args, &cypher_dsl)
310
+ With.new(clause_list, :match, self, *args, &cypher_dsl)
311
+ self
312
+ end
313
+
314
+ def create_path(*args, &cypher_dsl)
315
+ CreatePath.new(clause_list, self, *args, &cypher_dsl)
316
+ self
317
+ end
318
+
319
+ def create_unique_path(*args, &cypher_dsl)
320
+ CreatePath.new(clause_list, self, *args, &cypher_dsl).unique!
321
+ self
322
+ end
323
+
324
+
325
+ # This operator means related to, without regard to type or direction.
326
+ # @param [Symbol, #var_name] other either a node (Symbol, #var_name)
327
+ # @return [MatchRelLeft, MatchNode]
328
+ def <=>(other)
329
+ MatchStart.new_match_node(clause, other, :both).eval_context
330
+ end
331
+
332
+ # This operator means outgoing related to
333
+ # @param [Symbol, #var_name, String] other the relationship
334
+ # @return [MatchRelLeft, MatchNode]
335
+ def >(other)
336
+ MatchStart.new(clause).new_match_rel(other).eval_context
337
+ end
338
+
339
+ # This operator means any direction related to
340
+ # @param (see #>)
341
+ # @return [MatchRelLeft, MatchNode]
342
+ def -(other)
343
+ MatchStart.new(clause).new_match_rel(other).eval_context
344
+ end
345
+
346
+ # This operator means incoming related to
347
+ # @param (see #>)
348
+ # @return [MatchRelLeft, MatchNode]
349
+ def <(other)
350
+ MatchStart.new(clause).new_match_rel(other).eval_context
351
+ end
352
+
353
+ # Outgoing relationship to other node
354
+ # @param [Symbol, #var_name] other either a node (Symbol, #var_name)
355
+ # @return [MatchRelLeft, MatchNode]
356
+ def >>(other)
357
+ MatchStart.new_match_node(clause, other, :outgoing).eval_context
358
+ end
359
+
360
+ # Incoming relationship to other node
361
+ # @param [Symbol, #var_name] other either a node (Symbol, #var_name)
362
+ # @return [MatchRelLeft, MatchNode]
363
+ def <<(other)
364
+ MatchStart.new_match_node(clause, other, :incoming).eval_context
365
+ end
366
+
367
+ def outgoing(*rel_types)
368
+ node = _get_or_create_node(rel_types)
369
+ MatchStart.new(clause).new_match_rels(rel_types).eval_context > node
370
+ node.eval_context
371
+ end
372
+
373
+ def _get_or_create_node(rel_types)
374
+ rel_types.last.kind_of?(Matchable) ? rel_types.pop.clause : NodeVar.new(clause.clause_list)
375
+ end
376
+
377
+ def outgoing?(*rel_types)
378
+ node = _get_or_create_node(rel_types)
379
+ MatchStart.new(clause).new_match_rels?(rel_types).eval_context > node
380
+ node.eval_context
381
+ end
382
+
383
+ def incoming(*rel_types)
384
+ node = _get_or_create_node(rel_types)
385
+ MatchStart.new(clause).new_match_rels(rel_types).eval_context < node
386
+ node.eval_context
387
+ end
388
+
389
+ def incoming?(*rel_types)
390
+ node = _get_or_create_node(rel_types)
391
+ MatchStart.new(clause).new_match_rels?(rel_types).eval_context < node
392
+ node.eval_context
393
+ end
394
+
395
+ def both(*rel_types)
396
+ node = _get_or_create_node(rel_types)
397
+ MatchStart.new(clause).new_match_rels(rel_types).eval_context - node
398
+ node.eval_context
399
+ end
400
+
401
+ def both?(*rel_types)
402
+ node = _get_or_create_node(rel_types)
403
+ MatchStart.new(clause).new_match_rels?(rel_types).eval_context - node
404
+ node.eval_context
405
+ end
406
+ end
407
+
408
+
409
+ end
410
+ end
411
+ end