neo4j-cypher 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ module Neo4j
2
+ module Cypher
3
+
4
+ # Generates a Cypher string from a Ruby DSL
5
+ # The result returned by #to_s and the last Cypher return columns can be found #return_names.
6
+ # The cypher query will only be generated once - in the constructor.
7
+ class Result
8
+
9
+ def initialize(*args, &dsl_block)
10
+ @root = Neo4j::Cypher::RootClause.new
11
+ eval_context = @root.eval_context
12
+ to_dsl_args = args.map do |a|
13
+ case
14
+ when a.is_a?(Array) && a.first.respond_to?(:_java_node)
15
+ eval_context.node(*a)
16
+ when a.is_a?(Array) && a.first.respond_to?(:_java_rel)
17
+ eval_context.rel(*a)
18
+ when a.respond_to?(:_java_node)
19
+ eval_context.node(a)
20
+ when a.respond_to?(:_java_rel)
21
+ eval_context.rel(a)
22
+ else
23
+ raise "Illegal argument #{a.class}"
24
+ end
25
+
26
+ end
27
+ @root.execute(to_dsl_args, &dsl_block)
28
+ @result = @root.return_value
29
+ end
30
+
31
+ def return_names
32
+ @root.return_names
33
+ end
34
+
35
+ # Converts the DSL query to a cypher String which can be executed by cypher query engine.
36
+ def to_s
37
+ @result
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,47 @@
1
+ module Neo4j
2
+ module Cypher
3
+ # Wraps the Cypher query result.
4
+ # Loads the node and relationships wrapper if possible and use symbol as column keys.
5
+ # This is typically used in the native neo4j bindings since result does is not a Ruby enumerable with symbols as keys.
6
+ # @notice The result is a once forward read only Enumerable, work if you need to read the result twice - use #to_a
7
+ #
8
+ # @example
9
+ # result = Neo4j.query(@a, @b){|a,b| node(a,b).as(:n)}
10
+ # r = @query_result.to_a # can only loop once
11
+ # r.size.should == 2
12
+ # r.first.should include(:n)
13
+ # r[0][:n].neo_id.should == @a.neo_id
14
+ # r[1][:n].neo_id.should == @b.neo_id
15
+ class ResultWrapper
16
+ include Enumerable
17
+
18
+ # @return the original result from the Neo4j Cypher Engine, once forward read only !
19
+ attr_reader :source
20
+
21
+ def initialize(source)
22
+ @source = source
23
+ end
24
+
25
+ # @return [Array<Symbol>] the columns in the query result
26
+ def columns
27
+ @source.columns.map { |x| x.to_sym }
28
+ end
29
+
30
+ # for the Enumerable contract
31
+ def each
32
+ @source.each { |row| yield map(row) }
33
+ end
34
+
35
+ # Maps each row so that we can use symbols for column names.
36
+ # @private
37
+ def map(row)
38
+ out = {} # move to a real hash!
39
+ row.each do |key, value|
40
+ out[key.to_sym] = value.respond_to?(:wrapper) ? value.wrapper : value
41
+ end
42
+ out
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,124 @@
1
+ module Neo4j
2
+ module Cypher
3
+
4
+
5
+ # Can be used to skip result from a return clause
6
+ class Skip
7
+ include Clause
8
+
9
+ def initialize(clause_list, value, context)
10
+ super(clause_list, :skip, context)
11
+ @value = value
12
+ end
13
+
14
+ def to_cypher
15
+ @value
16
+ end
17
+ end
18
+
19
+ # Can be used to limit result from a return clause
20
+ class Limit
21
+ include Clause
22
+
23
+ def initialize(clause_list, value, context)
24
+ super(clause_list, :limit, context)
25
+ @value = value
26
+ end
27
+
28
+ def to_cypher
29
+ @value
30
+ end
31
+ end
32
+
33
+ class OrderBy
34
+ include Clause
35
+
36
+ def initialize(clause_list, context)
37
+ super(clause_list, :order_by, context)
38
+ @orders = []
39
+ end
40
+
41
+ def asc(props)
42
+ @orders << [:asc, props.map(&:clause)]
43
+ end
44
+
45
+ def desc(props)
46
+ @orders << [:desc, props.map(&:clause)]
47
+ end
48
+
49
+ def to_cypher
50
+ @orders.map do |pair|
51
+ if pair[0] == :asc
52
+ pair[1].map(&:return_value).join(', ')
53
+ else
54
+ pair[1].map(&:return_value).join(', ') + " DESC"
55
+ end
56
+ end.join(', ')
57
+ end
58
+ end
59
+
60
+ # Used for returning several values, e.g. RETURN x,y,z
61
+ class Return
62
+ include Clause
63
+
64
+ attr_reader :return_items
65
+
66
+ def initialize(clause_list, return_items, opts = {})
67
+ super(clause_list, :return, EvalContext)
68
+ @return_items = return_items.map { |ri| ri.is_a?(ReturnItem::EvalContext) ? ri.clause : ReturnItem.new(clause_list, ri) }
69
+ opts.each_pair { |k, v| self.eval_context.send(k, v) }
70
+ end
71
+
72
+ def to_cypher
73
+ @return_items.map(&:return_value_with_alias).join(',')
74
+ end
75
+
76
+
77
+ class EvalContext
78
+ include Context
79
+ include ReturnOrder
80
+ end
81
+
82
+ end
83
+
84
+ # The return statement in the cypher query
85
+ class ReturnItem
86
+ include Clause
87
+ include Referenceable
88
+
89
+ def initialize(clause_list, name_or_ref)
90
+ super(clause_list, :return_item, EvalContext)
91
+ if name_or_ref.respond_to?(:clause)
92
+ @delegated_clause = name_or_ref.clause
93
+ @delegated_clause.referenced!
94
+ as_alias(@delegated_clause.var_name) if @delegated_clause.as_alias?
95
+ else
96
+ @return_value = name_or_ref.to_s
97
+ end
98
+ end
99
+
100
+ def var_name
101
+ @var_name || (@delegated_clause && @delegated_clause.var_name) || @return_value.to_sym
102
+ end
103
+
104
+ def return_value_with_alias
105
+ as_alias? ? "#{return_value} as #{var_name}" : return_value
106
+ end
107
+
108
+ def return_value
109
+ @delegated_clause ? @delegated_clause.return_value : @return_value
110
+ end
111
+
112
+ class EvalContext
113
+ include Context
114
+ include Alias
115
+ include ReturnOrder
116
+ include Aggregate
117
+ include Comparable
118
+ end
119
+
120
+
121
+ end
122
+ end
123
+
124
+ end
@@ -0,0 +1,182 @@
1
+ module Neo4j
2
+ module Cypher
3
+ class RootClause
4
+ include Clause
5
+
6
+ def initialize
7
+ super(ClauseList.new, :root, EvalContext)
8
+ end
9
+
10
+ def execute(args, &cypher_dsl)
11
+ result = eval_context.instance_exec(*args, &cypher_dsl)
12
+
13
+ if ![Array, Symbol].include?(result.class)
14
+ return if clause_list.include?(:return)
15
+ return if clause_list.include?(:with)
16
+ return if clause_list.include?(:delete)
17
+ end
18
+ create_returns(result)
19
+ end
20
+
21
+ def create_returns(last_result)
22
+ if last_result.is_a?(Array)
23
+ eval_context.ret(*last_result)
24
+ elsif last_result.nil?
25
+ eval_context.ret(clause_list.last.eval_context) unless clause_list.empty?
26
+ else
27
+ eval_context.ret(last_result)
28
+ end
29
+ end
30
+
31
+ def return_value
32
+ clause_list.to_cypher
33
+ end
34
+
35
+ def return_names
36
+ ret = clause_list.last
37
+ ret.respond_to?(:return_items) ? ret.return_items.map { |ri| ri.var_name.to_sym } : []
38
+ end
39
+
40
+ class EvalContext
41
+ include Context
42
+ include MathFunctions
43
+ include Returnable
44
+
45
+ # Does nothing, just for making the DSL easier to read (maybe).
46
+ # @return self
47
+ def match(*, &match_dsl)
48
+ instance_eval(&match_dsl) if match_dsl
49
+ self
50
+ end
51
+
52
+ def match_not(&match_dsl)
53
+ instance_eval(&match_dsl).not
54
+ self
55
+ end
56
+
57
+ # Does nothing, just for making the DSL easier to read (maybe)
58
+ # @return self
59
+ def start(*)
60
+ self
61
+ end
62
+
63
+ def where(w=nil)
64
+ Where.new(clause_list, w) if w.is_a?(String)
65
+ self
66
+ end
67
+
68
+ # Specifies a start node by performing a lucene query.
69
+ # @param [Class] index_class a class responsible for an index
70
+ # @param [String] q the lucene query
71
+ # @param [Symbol] index_type the type of index
72
+ # @return [NodeQuery]
73
+ def query(index_class, q, index_type = :exact)
74
+ NodeQuery.new(clause_list, index_class, q, index_type).eval_context
75
+ end
76
+
77
+ # Specifies a start node by performing a lucene query.
78
+ # @param [Class] index_class a class responsible for an index
79
+ # @param [String, Symbol] key the key we ask for
80
+ # @param [String, Symbol] value the value of the key we ask for
81
+ # @return [NodeLookup]
82
+ def lookup(index_class, key, value)
83
+ NodeLookup.new(clause_list, index_class, key, value).eval_context
84
+ end
85
+
86
+ # Creates a node variable.
87
+ # It will create different variables depending on the type of the first element in the nodes argument.
88
+ # * Fixnum - it will be be used as neo_id for start node(s) (StartNode)
89
+ # * Symbol - it will create an unbound node variable with the same name as the symbol (NodeVar#as)
90
+ # * empty array - it will create an unbound node variable (NodeVar)
91
+ #
92
+ # @param [Fixnum,Symbol,String] nodes the id of the nodes we want to start from
93
+ # @return [StartNode, NodeVar]
94
+ def node(*nodes)
95
+ if nodes.first.is_a?(Symbol)
96
+ NodeVar.new(clause_list).eval_context.as(nodes.first)
97
+ elsif !nodes.empty?
98
+ StartNode.new(clause_list, nodes).eval_context
99
+ else
100
+ NodeVar.new(clause_list).eval_context
101
+ end
102
+ end
103
+
104
+ # Similar to #node
105
+ # @return [StartRel, RelVar]
106
+ def rel(*rels)
107
+ if rels.first.is_a?(Fixnum) || rels.first.respond_to?(:neo_id)
108
+ StartRel.new(clause_list, rels).eval_context
109
+ elsif rels.first.is_a?(Symbol)
110
+ RelVar.new(clause_list, ":`#{rels.first}`", rels[1]).eval_context
111
+ elsif rels.first.is_a?(String)
112
+ RelVar.new(clause_list, rels.first, rels[1]).eval_context
113
+ elsif rels.empty?
114
+ RelVar.new(clause_list, '?').eval_context
115
+ else
116
+ raise "Unknown arg #{rels.inspect}"
117
+ end
118
+ end
119
+
120
+ def rel?(*rels)
121
+ rel(*rels).clause.optionally!.eval_context
122
+ end
123
+
124
+
125
+ def shortest_path(&block)
126
+ match = instance_eval(&block)
127
+ match.shortest_path
128
+ end
129
+
130
+ def shortest_paths(&block)
131
+ match = instance_eval(&block)
132
+ match.shortest_paths
133
+ end
134
+
135
+ # @param [Symbol,nil] variable the entity we want to count or wildcard (*)
136
+ # @return [ReturnItem] a counter return clause
137
+ def count(variable='*')
138
+ operand = variable.respond_to?(:clause) ? variable.clause.var_name : variable
139
+ ReturnItem.new(clause_list, "count(#{operand})").eval_context
140
+ end
141
+
142
+ def coalesce(*args)
143
+ s = args.map { |x| x.clause.return_value }.join(", ")
144
+ ReturnItem.new(clause_list, "coalesce(#{s})").eval_context
145
+ end
146
+
147
+ def nodes(*args)
148
+ s = args.map { |x| x.clause.referenced!; x.clause.var_name }.join(", ")
149
+ ReturnItem.new(clause_list, "nodes(#{s})").eval_context
150
+ end
151
+
152
+ def rels(*args)
153
+ s = args.map { |x| x.clause.referenced!; x.clause.var_name }.join(", ")
154
+ ReturnItem.new(clause_list, "relationships(#{s})").eval_context
155
+ end
156
+
157
+ def create_path(*args, &block)
158
+ CreatePath.new(clause_list, *args, &block).eval_context
159
+ end
160
+
161
+ def create_unique_path(*args, &block)
162
+ CreatePath.new(clause_list, *args, &block).unique!.eval_context
163
+ end
164
+
165
+ def with(*args, &block)
166
+ With.new(clause_list, :where, *args, &block).eval_context
167
+ end
168
+
169
+ def with_match(*args, &block)
170
+ With.new(clause_list, :match, *args, &block).eval_context
171
+ end
172
+
173
+ def distinct(node_or_name)
174
+ operand = node_or_name.respond_to?(:clause) ? node_or_name.clause.var_name : node_or_name
175
+ ReturnItem.new(clause_list, "distinct(#{operand})").eval_context
176
+ end
177
+
178
+ end
179
+ end
180
+
181
+ end
182
+ end
@@ -0,0 +1,88 @@
1
+ module Neo4j
2
+ module Cypher
3
+ class Start
4
+ include Clause
5
+ include Referenceable
6
+
7
+ attr_accessor :entities # TODO CHECK if needed
8
+
9
+ def initialize(clause_list)
10
+ super(clause_list, :start, EvalContext)
11
+ end
12
+
13
+ def initialize_entities(entities)
14
+ @entities = entities.map { |n| n.respond_to?(:neo_id) ? n.neo_id : n }
15
+ end
16
+
17
+ class EvalContext
18
+ include Context
19
+ include Variable
20
+ include Matchable
21
+ include Returnable
22
+ include Sortable
23
+ include Aggregate
24
+ include Alias
25
+ end
26
+
27
+ end
28
+
29
+ # Can be created from a <tt>node</tt> dsl method.
30
+ class StartNode < Start
31
+
32
+ def initialize(clause_list, nodes)
33
+ super(clause_list)
34
+ initialize_entities(nodes)
35
+ end
36
+
37
+ def to_cypher
38
+ "#{var_name}=node(#{entities.join(',')})"
39
+ end
40
+
41
+ end
42
+
43
+
44
+ # Can be created from a <tt>rel</tt> dsl method.
45
+ class StartRel < Start
46
+ def initialize(clause_list, rels)
47
+ super(clause_list)
48
+ initialize_entities(rels)
49
+ end
50
+
51
+ def to_cypher
52
+ "#{var_name}=relationship(#{entities.join(',')})"
53
+ end
54
+ end
55
+
56
+ class NodeQuery < Start
57
+ attr_reader :index_name, :query
58
+
59
+ def initialize(clause_list, index_class, query, index_type)
60
+ super(clause_list)
61
+ @index_name = index_class.index_name_for_type(index_type)
62
+ @query = query
63
+ end
64
+
65
+ def to_cypher
66
+ "#{var_name}=node:#{index_name}(#{query})"
67
+ end
68
+ end
69
+
70
+ class NodeLookup < Start
71
+ attr_reader :index_name, :query
72
+
73
+ def initialize(clause_list, index_class, key, value)
74
+ super(clause_list)
75
+ index_type = index_class.index_type(key.to_s)
76
+ raise "No index on #{index_class} property #{key}" unless index_type
77
+ @index_name = index_class.index_name_for_type(index_type)
78
+ @query = %Q[#{key}="#{value}"]
79
+ end
80
+
81
+ def to_cypher
82
+ %Q[#{var_name}=node:#{index_name}(#{query})]
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+ end