neo4j-core 4.0.7 → 5.0.0.rc.1

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.
@@ -6,6 +6,7 @@ module Neo4j
6
6
 
7
7
  def initialize(node, match)
8
8
  @node = node
9
+ ::Neo4j::Node.validate_match!(match)
9
10
  @match = match
10
11
  end
11
12
 
@@ -46,7 +47,7 @@ module Neo4j
46
47
  end
47
48
  end
48
49
 
49
- class EmbeddedNode
50
+ class EmbeddedNode < Neo4j::Node
50
51
  class << self
51
52
  # This method is used to extend a Java Neo4j class so that it includes the same mixins as this class.
52
53
  Java::OrgNeo4jKernelImplCore::NodeProxy.class_eval do
@@ -149,41 +150,32 @@ module Neo4j
149
150
  tx_methods :rel
150
151
 
151
152
  def _rel(match = {})
152
- dir = ToJava.dir_to_java(match[:dir] || :both)
153
- rel_type = match[:type]
153
+ dir, rel_type, between_id = _parse_match(match)
154
154
 
155
155
  rel = if rel_type
156
- get_single_relationship(ToJava.type_to_java(rel_type), dir)
156
+ get_single_relationship(rel_type, dir)
157
157
  else
158
- iter = get_relationships(dir).iterator
159
-
160
- if iter.has_next
161
- first = iter.next
162
- fail "Expected to only find one relationship from node #{neo_id} matching #{match.inspect}" if iter.has_next
163
- first
164
- end
158
+ _get_single_relationship_by_dir(dir)
165
159
  end
166
160
 
167
- between_id = match[:between] && match[:between].neo_id
161
+ rel if !(rel && between_id) || rel.other_node(self).neo_id == between_id
162
+ end
168
163
 
169
- if rel && between_id
170
- rel if rel.other_node(self).neo_id == between_id
171
- else
172
- rel
173
- end
164
+ def _get_single_relationship_by_dir(dir)
165
+ iter = get_relationships(dir).iterator
166
+
167
+ return nil if not iter.has_next
168
+
169
+ first = iter.next
170
+ fail "Expected to only find one relationship from node #{neo_id} matching #{match.inspect}" if iter.has_next
171
+ first
174
172
  end
175
173
 
176
174
  def _rels(match = {})
177
- dir = match[:dir] || :both
178
- rel_type = match[:type]
175
+ dir, rel_type, between_id = _parse_match(match)
179
176
 
180
- rels = if rel_type
181
- get_relationships(ToJava.type_to_java(rel_type), ToJava.dir_to_java(dir)).iterator
182
- else
183
- get_relationships(ToJava.dir_to_java(dir)).iterator
184
- end
185
-
186
- between_id = match[:between] && match[:between].neo_id
177
+ args = rel_type ? [rel_type, dir] : [dir]
178
+ rels = get_relationships(*args).iterator
187
179
 
188
180
  if between_id
189
181
  rels.find_all { |r| r.end_node.neo_id == between_id || r.start_node.neo_id == between_id }
@@ -192,8 +184,14 @@ module Neo4j
192
184
  end
193
185
  end
194
186
 
195
- def class
196
- Neo4j::Node
187
+ def _parse_match(match)
188
+ ::Neo4j::Node.validate_match!(match)
189
+
190
+ [
191
+ ToJava.dir_to_java(match[:dir] || :both),
192
+ ToJava.type_to_java(match[:type]),
193
+ match[:between] && match[:between].neo_id
194
+ ]
197
195
  end
198
196
 
199
197
  include Neo4j::Node::Wrapper
@@ -16,7 +16,6 @@ module Neo4j
16
16
  extend Neo4j::Core::TxMethods
17
17
  def_delegator :@graph_db, :begin_tx
18
18
 
19
-
20
19
  def initialize(db_location, config = {})
21
20
  @db_location = db_location
22
21
  @auto_commit = !!config[:auto_commit]
@@ -34,7 +33,7 @@ module Neo4j
34
33
 
35
34
  def version
36
35
  # Wow
37
- version_string = graph_db.to_java(Java::OrgNeo4jKernel::GraphDatabaseAPI).getDependencyResolver.resolveDependency(Java::OrgNeo4jKernel::KernelData.java_class).version.to_s
36
+ version_string = @graph_db.to_java(Java::OrgNeo4jKernel::GraphDatabaseAPI).getDependencyResolver.resolveDependency(Java::OrgNeo4jKernel::KernelData.java_class).version.to_s
38
37
  version_string.split(' ')[-1]
39
38
  end
40
39
 
@@ -69,12 +68,14 @@ module Neo4j
69
68
  end
70
69
 
71
70
  def shutdown
72
- graph_db && graph_db.shutdown
71
+ @graph_db && @graph_db.shutdown
72
+
73
+ Neo4j::Session.clear_listeners
73
74
  @graph_db = nil
74
75
  end
75
76
 
76
77
  def running?
77
- !!graph_db
78
+ !!@graph_db
78
79
  end
79
80
 
80
81
  def create_label(name)
@@ -152,10 +153,10 @@ module Neo4j
152
153
 
153
154
  def create_node(properties = nil, labels = [])
154
155
  if labels.empty?
155
- graph_db.create_node
156
+ @graph_db.create_node
156
157
  else
157
158
  labels = EmbeddedLabel.as_java(labels)
158
- graph_db.create_node(labels)
159
+ @graph_db.create_node(labels)
159
160
  end.tap do |java_node|
160
161
  properties.each_pair { |k, v| java_node[k] = v } if properties
161
162
  end
@@ -22,12 +22,12 @@ module Neo4j
22
22
  "EmbeddedTransaction [nested: #{@pushed_nested} failed?: #{failure?} active: #{Neo4j::Transaction.current == self}]"
23
23
  end
24
24
 
25
- def _delete_tx
25
+ def delete
26
26
  @root_tx.failure
27
27
  @root_tx.close
28
28
  end
29
29
 
30
- def _commit_tx
30
+ def commit
31
31
  @root_tx.success
32
32
  @root_tx.close
33
33
  end
@@ -0,0 +1,65 @@
1
+ module Neo4j
2
+ class Label
3
+ class << self
4
+ def indexes
5
+ schema_query(:get_indexes)
6
+ end
7
+
8
+ def constraints
9
+ schema_query(:get_constraints)
10
+ end
11
+
12
+ def index?(label, property)
13
+ schema_index_operation(label, property, :indexes)
14
+ end
15
+
16
+ def constraint?(label, property)
17
+ schema_index_operation(label, property, :constraints)
18
+ end
19
+
20
+ def drop_all_indexes
21
+ Neo4j::Transaction.run do
22
+ schema.get_indexes.to_a.each do |i|
23
+ begin
24
+ i.drop
25
+ rescue Java::JavaLang::IllegalStateException; end
26
+ end
27
+ end
28
+ end
29
+
30
+ def drop_all_constraints
31
+ Neo4j::Transaction.run do
32
+ schema.get_constraints.to_a.each(&:drop)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def schema_query(query_method)
39
+ [].tap do |index_array|
40
+ Neo4j::Transaction.run do
41
+ schema.send(query_method).to_a.each { |i| index_array << index_hash(i) }
42
+ end
43
+ end
44
+ end
45
+
46
+ def schema_index_operation(label, property, schema_method)
47
+ label = label.to_s
48
+ property = property.to_s
49
+ !send(schema_method).select { |i| matched_index(i, label, property) }.empty?
50
+ end
51
+
52
+ def index_hash(java_index)
53
+ {property_keys: java_index.get_property_keys.to_a, label: java_index.get_label.name}
54
+ end
55
+
56
+ def matched_index(java_index, label, property)
57
+ java_index[:property_keys].first == property && java_index[:label] == label
58
+ end
59
+
60
+ def schema
61
+ Neo4j::Session.current.graph_db.schema
62
+ end
63
+ end
64
+ end
65
+ end
@@ -75,15 +75,13 @@ module Neo4j
75
75
  end
76
76
 
77
77
  def to_java_property(k, v)
78
- validate_property(v)
78
+ validate_property!(v)
79
79
 
80
80
  k = k.to_s
81
81
  case v
82
- when nil
83
- remove_property(k)
82
+ when nil then remove_property(k)
84
83
  when Array
85
- type = java_type_from_value(v[0]) || fail("Not allowed to store array with value #{v[0]} type #{v[0].class}")
86
- set_property(k, v.to_java(type))
84
+ set_property(k, v.to_java(java_type_from_value(v[0])))
87
85
  else
88
86
  set_property(k, v)
89
87
  end
@@ -99,6 +97,8 @@ module Neo4j
99
97
  :boolean
100
98
  when Fixnum
101
99
  :long
100
+ else
101
+ fail "Not allowed to store array with value #{value.inspect} type #{value.class}"
102
102
  end
103
103
  end
104
104
  end
@@ -4,7 +4,7 @@ module Neo4j
4
4
  # @private
5
5
  module ToJava
6
6
  def type_to_java(type)
7
- Java::OrgNeo4jGraphdb::DynamicRelationshipType.withName(type.to_s)
7
+ type && Java::OrgNeo4jGraphdb::DynamicRelationshipType.withName(type.to_s)
8
8
  end
9
9
 
10
10
  module_function :type_to_java
@@ -18,12 +18,9 @@ module Neo4j
18
18
 
19
19
  def dir_from_java(dir)
20
20
  case dir
21
- when Java::OrgNeo4jGraphdb::Direction::OUTGOING then
22
- :outgoing
23
- when Java::OrgNeo4jGraphdb::Direction::BOTH then
24
- :both
25
- when Java::OrgNeo4jGraphdb::Direction::INCOMING then
26
- :incoming
21
+ when Java::OrgNeo4jGraphdb::Direction::OUTGOING then :outgoing
22
+ when Java::OrgNeo4jGraphdb::Direction::BOTH then :both
23
+ when Java::OrgNeo4jGraphdb::Direction::INCOMING then :incoming
27
24
  else
28
25
  fail "unknown direction '#{dir} / #{dir.class}'"
29
26
  end
@@ -33,12 +30,9 @@ module Neo4j
33
30
 
34
31
  def dir_to_java(dir)
35
32
  case dir
36
- when :outgoing then
37
- Java::OrgNeo4jGraphdb::Direction::OUTGOING
38
- when :both then
39
- Java::OrgNeo4jGraphdb::Direction::BOTH
40
- when :incoming then
41
- Java::OrgNeo4jGraphdb::Direction::INCOMING
33
+ when :outgoing then Java::OrgNeo4jGraphdb::Direction::OUTGOING
34
+ when :both then Java::OrgNeo4jGraphdb::Direction::BOTH
35
+ when :incoming then Java::OrgNeo4jGraphdb::Direction::INCOMING
42
36
  else
43
37
  fail "unknown direction '#{dir}', expects argument: outgoing, :incoming or :both"
44
38
  end
@@ -8,6 +8,7 @@ require 'neo4j-embedded/embedded_label'
8
8
  require 'neo4j-embedded/embedded_node'
9
9
  require 'neo4j-embedded/embedded_relationship'
10
10
  require 'neo4j-embedded/embedded_label'
11
+ require 'neo4j-embedded/label'
11
12
  require 'neo4j-embedded/embedded_transaction'
12
13
  require 'neo4j-embedded/cypher_response'
13
14
 
@@ -9,10 +9,9 @@ module Neo4j
9
9
  @session = session
10
10
 
11
11
  @neo_id = if value.is_a?(Hash)
12
- hash = value['data']
13
- @props = Hash[hash.map { |k, v| [k.to_sym, v] }]
14
- @labels = value['metadata']['labels'].map!(&:to_sym) if value['metadata']
15
- value['id'] # value['self'].match(/\d+$/)[0].to_i
12
+ @props = value[:data]
13
+ @labels = value[:metadata][:labels].map!(&:to_sym) if value[:metadata]
14
+ value[:id]
16
15
  else
17
16
  value
18
17
  end
@@ -31,15 +30,12 @@ module Neo4j
31
30
 
32
31
  # (see Neo4j::Node#create_rel)
33
32
  def create_rel(type, other_node, props = nil)
34
- ids_hash = {start_neo_id: neo_id, end_neo_id: other_node.neo_id}
35
- props_with_ids = props.nil? ? ids_hash : cypher_prop_list(props).merge(ids_hash)
36
- id = @session._query_or_fail(rel_string(type, other_node, props), true, props_with_ids)
37
- data_hash = {'type' => type, 'data' => props, 'start' => neo_id, 'end' => other_node.neo_id, 'id' => id}
38
- CypherRelationship.new(@session, data_hash)
39
- end
33
+ q = @session.query.match(:a, :b).where(a: {neo_id: neo_id}, b: {neo_id: other_node.neo_id})
34
+ .create("(a)-[r:`#{type}`]->(b)").break.set(r: props).return(r: :neo_id)
35
+
36
+ id = @session._query_or_fail(q, true)
40
37
 
41
- def rel_string(type, other_node, props)
42
- "MATCH (a), (b) WHERE ID(a) = {start_neo_id} AND ID(b) = {end_neo_id} CREATE (a)-[r:`#{type}` #{prop_identifier(props)}]->(b) RETURN ID(r)"
38
+ CypherRelationship.new(@session, type: type, data: props, start: neo_id, end: other_node.neo_id, id: id)
43
39
  end
44
40
 
45
41
  # (see Neo4j::Node#props)
@@ -47,8 +43,8 @@ module Neo4j
47
43
  if @props
48
44
  @props
49
45
  else
50
- hash = @session._query_entity_data("#{match_start} RETURN n", nil, neo_id: neo_id)
51
- @props = Hash[hash['data'].map { |k, v| [k.to_sym, v] }]
46
+ hash = @session._query_entity_data(match_start_query.return(:n), nil)
47
+ @props = Hash[hash[:data].to_a]
52
48
  end
53
49
  end
54
50
 
@@ -59,29 +55,27 @@ module Neo4j
59
55
  # (see Neo4j::Node#remove_property)
60
56
  def remove_property(key)
61
57
  refresh
62
- @session._query_or_fail("#{match_start} REMOVE n.`#{key}`", false, neo_id: neo_id)
58
+ @session._query_or_fail(match_start_query.remove(n: key), false)
63
59
  end
64
60
 
65
61
  # (see Neo4j::Node#set_property)
66
62
  def set_property(key, value)
67
63
  refresh
68
- @session._query_or_fail("#{match_start} SET n.`#{key}` = { value }", false, value: value, neo_id: neo_id)
69
- value
64
+ @session._query_or_fail(match_start_query.set(n: {key => value}), false)
70
65
  end
71
66
 
72
67
  # (see Neo4j::Node#props=)
73
68
  def props=(properties)
74
69
  refresh
75
- @session._query_or_fail("#{match_start} SET n = { props }", false, props: properties, neo_id: neo_id)
70
+ @session._query_or_fail(match_start_query.set_props(n: properties), false)
76
71
  properties
77
72
  end
78
73
 
79
74
  def remove_properties(properties)
75
+ return if properties.empty?
76
+
80
77
  refresh
81
- q = "#{match_start} REMOVE " + properties.map do |k|
82
- "n.`#{k}`"
83
- end.join(', ')
84
- @session._query_or_fail(q, false, neo_id: neo_id)
78
+ @session._query_or_fail(match_start_query.remove(n: properties), false, neo_id: neo_id)
85
79
  end
86
80
 
87
81
  # (see Neo4j::Node#update_props)
@@ -89,23 +83,19 @@ module Neo4j
89
83
  refresh
90
84
  return if properties.empty?
91
85
 
92
- removed_keys = properties.keys.select { |k| properties[k].nil? }
93
- remove_properties(removed_keys) unless removed_keys.empty?
94
- properties_to_set = properties.keys - removed_keys
95
- return if properties_to_set.empty?
96
- props_list = cypher_prop_list(properties)[:props].merge(neo_id: neo_id)
97
- @session._query_or_fail("#{match_start} SET #{cypher_properties(properties_to_set)}", false, props_list)
86
+ @session._query_or_fail(match_start_query.set(n: properties), false)
87
+
98
88
  properties
99
89
  end
100
90
 
101
91
  # (see Neo4j::Node#get_property)
102
92
  def get_property(key)
103
- @props ? @props[key.to_sym] : @session._query_or_fail("#{match_start} RETURN n.`#{key}`", true, neo_id: neo_id)
93
+ @props ? @props[key.to_sym] : @session._query_or_fail(match_start_query.return(n: key), true)
104
94
  end
105
95
 
106
96
  # (see Neo4j::Node#labels)
107
97
  def labels
108
- @labels ||= @session._query_or_fail("#{match_start} RETURN labels(n) as labels", true, neo_id: neo_id).map!(&:to_sym)
98
+ @labels ||= @session._query_or_fail(match_start_query.return('labels(n) AS labels'), true).map(&:to_sym)
109
99
  end
110
100
 
111
101
  def _cypher_label_list(labels_list)
@@ -113,18 +103,29 @@ module Neo4j
113
103
  end
114
104
 
115
105
  def add_label(*new_labels)
116
- @session._query_or_fail("#{match_start} SET n #{_cypher_label_list(new_labels)}", false, neo_id: neo_id)
106
+ @session._query_or_fail(match_start_query.set(n: new_labels), false)
117
107
  new_labels.each { |label| labels << label }
118
108
  end
119
109
 
120
110
  def remove_label(*target_labels)
121
- @session._query_or_fail("#{match_start} REMOVE n #{_cypher_label_list(target_labels)}", false, neo_id: neo_id)
111
+ @session._query_or_fail(match_start_query.remove(n: target_labels), false)
122
112
  target_labels.each { |label| labels.delete(label) } unless labels.nil?
123
113
  end
124
114
 
125
115
  def set_label(*label_names)
126
- q = "#{match_start} #{remove_labels_if_needed} #{set_labels_if_needed(label_names)}"
127
- @session._query_or_fail(q, false, neo_id: neo_id)
116
+ q = match_start_query
117
+
118
+ labels_to_add = label_names.map(&:to_sym).uniq
119
+ labels_to_remove = labels - label_names
120
+
121
+ common_labels = labels & labels_to_add
122
+ labels_to_add -= common_labels
123
+ labels_to_remove -= common_labels
124
+
125
+ q = q.remove(n: labels_to_remove) unless labels_to_remove.empty?
126
+ q = q.set(n: labels_to_add) unless labels_to_add.empty?
127
+
128
+ @session._query_or_fail(q, false) unless (labels_to_add + labels_to_remove).empty?
128
129
  end
129
130
 
130
131
  # (see Neo4j::Node#del)
@@ -138,7 +139,7 @@ module Neo4j
138
139
 
139
140
  # (see Neo4j::Node#exist?)
140
141
  def exist?
141
- @session._query("#{match_start} RETURN ID(n)", neo_id: neo_id).data.empty? ? false : true
142
+ !@session._query(match_start_query.return(n: :neo_id)).data.empty?
142
143
  end
143
144
 
144
145
  # (see Neo4j::Node#node)
@@ -169,55 +170,44 @@ module Neo4j
169
170
 
170
171
  # @private
171
172
  def match(clazz, returns, match = {})
172
- to_dir = {outgoing: ->(rel) { "-#{rel}->" },
173
- incoming: ->(rel) { "<-#{rel}-" },
174
- both: ->(rel) { "-#{rel}-" }}
175
-
176
- cypher_rel = match[:type] ? "[r:`#{match[:type]}`]" : '[r]'
177
- between_id = match[:between] ? "MATCH (p) WHERE ID(p) = #{match[:between].neo_id}" : ''
178
- dir_func = to_dir[match[:dir] || :both]
179
- cypher = "#{match_start} #{between_id} MATCH (n)#{dir_func.call(cypher_rel)}(p) RETURN #{returns}"
180
- r = @session._query(cypher, neo_id: neo_id)
173
+ ::Neo4j::Node.validate_match!(match)
174
+
175
+ query = self.query
176
+
177
+ query = query.match(:p).where(p: {neo_id: match[:between].neo_id}) if match[:between]
178
+
179
+ r = query.match("(n)#{relationship_arrow(match)}(p)").return(returns).response
180
+
181
181
  r.raise_error if r.error?
182
- _map_result(r)
183
- end
184
182
 
185
- def _map_result(r)
186
183
  r.to_node_enumeration.map(&:result)
187
184
  end
188
185
 
189
- private
190
-
191
- def cypher_properties(properties_to_set)
192
- properties_to_set.map! { |k| "n.`#{k}` = {`#{k}`}" }.join(',')
186
+ def query(identifier = :n)
187
+ @session.query.match(identifier).where(identifier => {neo_id: neo_id})
193
188
  end
194
189
 
195
- def remove_labels_if_needed
196
- if labels.empty?
197
- ''
198
- else
199
- " REMOVE n #{_cypher_label_list(labels)}"
200
- end
201
- end
190
+ private
191
+
192
+ def relationship_arrow(match)
193
+ rel_spec = match[:type] ? "[r:`#{match[:type]}`]" : '[r]'
202
194
 
203
- def set_labels_if_needed(label_names)
204
- if label_names.empty?
205
- ''
195
+ case match[:dir] || :both
196
+ when :outgoing then "-#{rel_spec}->"
197
+ when :incoming then "<-#{rel_spec}-"
198
+ when :both then "-#{rel_spec}-"
206
199
  else
207
- " SET n #{_cypher_label_list(label_names.map(&:to_sym).uniq)}"
200
+ fail "Invalid value for relationship_arrow direction: #{match[:dir].inspect}"
208
201
  end
209
202
  end
210
203
 
211
- def ensure_single_relationship(&block)
204
+ def ensure_single_relationship
205
+ fail 'Expected a block' unless block_given?
212
206
  result = yield
213
207
  fail "Expected to only find one relationship from node #{neo_id} matching #{match.inspect} but found #{result.count}" if result.count > 1
214
208
  result.first
215
209
  end
216
210
 
217
- def match_start(identifier = 'n')
218
- "MATCH (#{identifier}) WHERE ID(#{identifier}) = {neo_id}"
219
- end
220
-
221
211
  def match_start_query(identifier = :n)
222
212
  @session.query.match(identifier).where(identifier => {neo_id: neo_id}).with(identifier)
223
213
  end
@@ -7,7 +7,7 @@ module Neo4j
7
7
  end
8
8
 
9
9
  def [](key)
10
- @data[key.to_s]
10
+ @data[key]
11
11
  end
12
12
  end
13
13
  end
@@ -8,11 +8,11 @@ module Neo4j
8
8
  def initialize(session, value)
9
9
  @session = session
10
10
  @response_hash = value
11
- @rel_type = @response_hash['type']
12
- @props = @response_hash['data']
13
- @start_node_neo_id = @response_hash['start'].is_a?(Integer) ? @response_hash['start'] : @response_hash['start'].match(/\d+$/)[0].to_i
14
- @end_node_neo_id = @response_hash['end'].is_a?(Integer) ? @response_hash['end'] : @response_hash['end'].match(/\d+$/)[0].to_i
15
- @id = @response_hash['id']
11
+ @rel_type = @response_hash[:type]
12
+ @props = @response_hash[:data]
13
+ @start_node_neo_id = neo_id_integer(@response_hash[:start])
14
+ @end_node_neo_id = neo_id_integer(@response_hash[:end])
15
+ @id = @response_hash[:id]
16
16
  end
17
17
 
18
18
  def ==(other)
@@ -80,7 +80,7 @@ module Neo4j
80
80
  @props
81
81
  else
82
82
  hash = @session._query_entity_data("#{match_start} RETURN n", nil, neo_id: neo_id)
83
- @props = Hash[hash['data'].map { |k, v| [k.to_sym, v] }]
83
+ @props = Hash[hash[:data].map { |k, v| [k, v] }]
84
84
  end
85
85
  end
86
86
 
@@ -125,6 +125,10 @@ module Neo4j
125
125
  def resource_data_present?
126
126
  !resource_data.nil? && !resource_data.empty?
127
127
  end
128
+
129
+ def neo_id_integer(id_or_url)
130
+ id_or_url.is_a?(Integer) ? id_or_url : id_or_url.match(/\d+$/)[0].to_i
131
+ end
128
132
  end
129
133
  end
130
134
  end