neo4j-core 3.1.1 → 4.0.0
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/Gemfile +7 -12
- data/README.md +7 -7
- data/lib/neo4j-core.rb +3 -2
- data/lib/neo4j-core/active_entity.rb +8 -10
- data/lib/neo4j-core/cypher_translator.rb +61 -59
- data/lib/neo4j-core/hash_with_indifferent_access.rb +31 -22
- data/lib/neo4j-core/helpers.rb +15 -17
- data/lib/neo4j-core/label.rb +7 -6
- data/lib/neo4j-core/query.rb +271 -268
- data/lib/neo4j-core/query_clauses.rb +371 -355
- data/lib/neo4j-core/query_find_in_batches.rb +26 -26
- data/lib/neo4j-core/version.rb +1 -1
- data/lib/neo4j-embedded.rb +2 -2
- data/lib/neo4j-embedded/cypher_response.rb +40 -41
- data/lib/neo4j-embedded/embedded_database.rb +21 -22
- data/lib/neo4j-embedded/embedded_ha_session.rb +13 -11
- data/lib/neo4j-embedded/embedded_impermanent_session.rb +9 -8
- data/lib/neo4j-embedded/embedded_label.rb +64 -70
- data/lib/neo4j-embedded/embedded_node.rb +68 -73
- data/lib/neo4j-embedded/embedded_relationship.rb +6 -13
- data/lib/neo4j-embedded/embedded_session.rb +128 -132
- data/lib/neo4j-embedded/embedded_transaction.rb +34 -33
- data/lib/neo4j-embedded/property.rb +84 -77
- data/lib/neo4j-embedded/to_java.rb +24 -23
- data/lib/neo4j-server.rb +1 -1
- data/lib/neo4j-server/cypher_authentication.rb +105 -103
- data/lib/neo4j-server/cypher_label.rb +25 -23
- data/lib/neo4j-server/cypher_node.rb +180 -177
- data/lib/neo4j-server/cypher_node_uncommited.rb +11 -9
- data/lib/neo4j-server/cypher_relationship.rb +101 -102
- data/lib/neo4j-server/cypher_response.rb +171 -170
- data/lib/neo4j-server/cypher_session.rb +209 -205
- data/lib/neo4j-server/cypher_transaction.rb +66 -48
- data/lib/neo4j-server/resource.rb +17 -22
- data/lib/neo4j/entity_equality.rb +3 -4
- data/lib/neo4j/label.rb +13 -16
- data/lib/neo4j/node.rb +30 -34
- data/lib/neo4j/property_container.rb +3 -3
- data/lib/neo4j/property_validator.rb +4 -5
- data/lib/neo4j/relationship.rb +17 -22
- data/lib/neo4j/session.rb +19 -21
- data/lib/neo4j/tasks/config_server.rb +2 -3
- data/lib/neo4j/tasks/neo4j_server.rake +82 -74
- data/lib/neo4j/transaction.rb +23 -22
- data/neo4j-core.gemspec +21 -16
- metadata +72 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e441fd4d968dfd3b6caf201fc60ce19c2698d29
|
4
|
+
data.tar.gz: d4e88b57b2ecd1a342eaa83c4c05faa1c4d3bdbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65292755970b43e9c31b886c1e3e52455094f7de61e44d483f88c15d9f4ec713a9ceeff2302b0918c02264ee957e08210850aa29ed3651905bcd5149165916dc
|
7
|
+
data.tar.gz: b3ef3555c5b6de4ef7527664b2133ecf0ca73de25245b41bf2f92817800e7403010155ef48d7be9f583d8380280fbb431016f7f239184a3782110ffc925845e6
|
data/Gemfile
CHANGED
@@ -4,22 +4,17 @@ gemspec
|
|
4
4
|
|
5
5
|
gem 'zip'
|
6
6
|
|
7
|
-
#gem 'neo4j-advanced', '>= 1.8.1', '< 2.0', :require => false
|
8
|
-
#gem 'neo4j-enterprise', '>= 1.8.1', '< 2.0', :require => false
|
9
|
-
|
10
|
-
gem 'coveralls', require: false
|
11
|
-
gem 'simplecov-html', require: false
|
7
|
+
# gem 'neo4j-advanced', '>= 1.8.1', '< 2.0', :require => false
|
8
|
+
# gem 'neo4j-enterprise', '>= 1.8.1', '< 2.0', :require => false
|
12
9
|
|
13
10
|
group 'development' do
|
14
|
-
gem '
|
15
|
-
gem 'simplecov'
|
16
|
-
gem 'pry'
|
17
|
-
gem 'pry-rescue'
|
18
|
-
gem 'pry-stack_explorer', platform: :ruby
|
11
|
+
gem 'guard-rspec', require: false
|
19
12
|
end
|
20
13
|
|
21
14
|
group 'test' do
|
22
|
-
gem
|
23
|
-
gem
|
15
|
+
gem 'coveralls', require: false
|
16
|
+
gem 'simplecov-html', require: false
|
17
|
+
gem 'rake', '>= 0.8.7'
|
18
|
+
gem 'rspec', '~> 3.0'
|
24
19
|
gem 'rspec-its'
|
25
20
|
end
|
data/README.md
CHANGED
@@ -1,12 +1,12 @@
|
|
1
|
-
# Neo4j-core v3.0 [](https://codeclimate.com/github/neo4jrb/neo4j-core) [](https://travis-ci.org/neo4jrb/neo4j-core) [](https://coveralls.io/r/neo4jrb/neo4j-core?branch=master) [](https://www.pullreview.com/github/neo4jrb/neo4j-core/reviews/master)
|
2
2
|
|
3
3
|
A simple Ruby wrapper around the Neo4j graph database that works with the server and embedded Neo4j API. This gem can be used both from JRuby and normal MRI.
|
4
4
|
It can be used standalone without the neo4j gem.
|
5
5
|
|
6
6
|
## Documentation
|
7
7
|
|
8
|
-
* [3.0 Documentation](https://github.com/
|
9
|
-
* [2.x Documentation](https://github.com/
|
8
|
+
* [3.0 Documentation](https://github.com/neo4jrb/neo4j-core/wiki)
|
9
|
+
* [2.x Documentation](https://github.com/neo4jrb/neo4j-core/tree/v2.x)
|
10
10
|
|
11
11
|
|
12
12
|
## Support
|
@@ -16,20 +16,20 @@ It can be used standalone without the neo4j gem.
|
|
16
16
|
|
17
17
|
## Developers
|
18
18
|
|
19
|
-
* [Andreas Ronge](https://github.com/
|
19
|
+
* [Andreas Ronge](https://github.com/neo4jrb)
|
20
20
|
* [Brian Underwood](https://github.com/cheerfulstoic)
|
21
21
|
* [Chris Grigg](https://github.com/subvertallchris)
|
22
22
|
|
23
23
|
|
24
24
|
## Contributing
|
25
25
|
|
26
|
-
Pull request with high test coverage and good [code climate](https://codeclimate.com/github/
|
26
|
+
Pull request with high test coverage and good [code climate](https://codeclimate.com/github/neo4jrb/neo4j-core) values will be accepted faster.
|
27
27
|
Notice, only JRuby can run all the tests (embedded and server db). To run tests with coverage: `rake coverage`.
|
28
28
|
|
29
29
|
## License
|
30
|
-
* Neo4j.rb - MIT, see the LICENSE file http://github.com/
|
30
|
+
* Neo4j.rb - MIT, see the LICENSE file http://github.com/neo4jrb/neo4j-core/tree/master/LICENSE.
|
31
31
|
* Lucene - Apache, see http://lucene.apache.org/java/docs/features.html
|
32
|
-
*
|
32
|
+
* Neo4j - Dual free software/commercial license, see http://neo4j.org/
|
33
33
|
|
34
34
|
Notice there are different license for the neo4j-community, neo4j-advanced and neo4j-enterprise jar gems.
|
35
35
|
Only the neo4j-community gem is by default required.
|
data/lib/neo4j-core.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Core
|
3
|
+
# A module to make Neo4j::Node and Neo4j::Relationship work better together with neo4j.rb's Neo4j::ActiveNode and Neo4j::ActiveRel
|
4
|
+
module ActiveEntity
|
5
|
+
# @return true
|
6
|
+
def persisted?
|
7
|
+
true
|
8
|
+
end
|
8
9
|
end
|
9
|
-
|
10
10
|
end
|
11
11
|
end
|
12
|
-
|
13
|
-
|
@@ -1,74 +1,76 @@
|
|
1
|
-
module Neo4j
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Core
|
3
|
+
module CypherTranslator
|
4
|
+
# Cypher Helper
|
5
|
+
def escape_value(value)
|
6
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
7
|
+
"'#{escape_quotes(sanitize_escape_sequences(value.to_s))}'"
|
8
|
+
else
|
9
|
+
value
|
10
|
+
end
|
9
11
|
end
|
10
|
-
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
# Like escape_value but it does not wrap the value in quotes
|
14
|
+
def create_escape_value(value)
|
15
|
+
if value.is_a?(String) || value.is_a?(Symbol)
|
16
|
+
"#{sanitize_escape_sequences(value.to_s)}"
|
17
|
+
else
|
18
|
+
value
|
19
|
+
end
|
18
20
|
end
|
19
|
-
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
22
|
+
# Only following escape sequence characters are allowed in Cypher:
|
23
|
+
#
|
24
|
+
# \t Tab
|
25
|
+
# \b Backspace
|
26
|
+
# \n Newline
|
27
|
+
# \r Carriage return
|
28
|
+
# \f Form feed
|
29
|
+
# \' Single quote
|
30
|
+
# \" Double quote
|
31
|
+
# \\ Backslash
|
32
|
+
#
|
33
|
+
# From:
|
34
|
+
# http://docs.neo4j.org/chunked/stable/cypher-expressions.html#_note_on_string_literals
|
35
|
+
SANITIZE_ESCAPED_REGEXP = /(?<!\\)\\(\\\\)*(?![futbnr'"\\])/
|
36
|
+
EMPTY_PROPS = ''
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def sanitize_escape_sequences(s)
|
39
|
+
s.gsub SANITIZE_ESCAPED_REGEXP, EMPTY_PROPS
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
def escape_quotes(s)
|
43
|
+
s.gsub("'", %q(\\\'))
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
46
|
+
# Cypher Helper
|
47
|
+
def cypher_prop_list(props)
|
48
|
+
return nil unless props
|
49
|
+
props.reject! { |_, v| v.nil? }
|
50
|
+
{props: props.each { |k, v| props[k] = create_escape_value(v) }}
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
53
|
+
# Stolen from keymaker
|
54
|
+
# https://github.com/therubymug/keymaker/blob/master/lib/keymaker/parsers/cypher_response_parser.rb
|
55
|
+
def self.translate_response(response_body, result)
|
56
|
+
Hashie::Mash.new(Hash[sanitized_column_names(response_body).zip(result)])
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
59
|
+
def self.sanitized_column_names(response_body)
|
60
|
+
response_body.columns.map { |column| column[/[^\.]+$/] }
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
def cypher_string(labels, props)
|
64
|
+
"CREATE (n#{label_string(labels)} #{prop_identifier(props)}) RETURN ID(n)"
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
def label_string(labels)
|
68
|
+
labels.empty? ? '' : ":#{labels.map { |k| "`#{k}`" }.join(':')}"
|
69
|
+
end
|
69
70
|
|
70
|
-
|
71
|
-
|
71
|
+
def prop_identifier(props)
|
72
|
+
'{props}' unless props.nil?
|
73
|
+
end
|
72
74
|
end
|
73
75
|
end
|
74
76
|
end
|
@@ -3,7 +3,6 @@ module Neo4j
|
|
3
3
|
# Stolen from http://as.rubyonrails.org/classes/HashWithIndifferentAccess.html
|
4
4
|
# We don't want to depend on active support
|
5
5
|
class HashWithIndifferentAccess < Hash
|
6
|
-
|
7
6
|
# Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
|
8
7
|
def extractable_options?
|
9
8
|
true
|
@@ -103,7 +102,7 @@ module Neo4j
|
|
103
102
|
# hash.values_at("a", "b") # => ["x", "y"]
|
104
103
|
#
|
105
104
|
def values_at(*indices)
|
106
|
-
indices.collect {|key| self[convert_key(key)]}
|
105
|
+
indices.collect { |key| self[convert_key(key)] }
|
107
106
|
end
|
108
107
|
|
109
108
|
# Returns an exact copy of the hash.
|
@@ -116,7 +115,7 @@ module Neo4j
|
|
116
115
|
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash.
|
117
116
|
# Does not overwrite the existing hash.
|
118
117
|
def merge(hash)
|
119
|
-
|
118
|
+
dup.update(hash)
|
120
119
|
end
|
121
120
|
|
122
121
|
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
|
@@ -126,7 +125,7 @@ module Neo4j
|
|
126
125
|
end
|
127
126
|
|
128
127
|
def reverse_merge!(other_hash)
|
129
|
-
replace(reverse_merge(
|
128
|
+
replace(reverse_merge(other_hash))
|
130
129
|
end
|
131
130
|
|
132
131
|
# Removes a specified key from the hash.
|
@@ -134,11 +133,21 @@ module Neo4j
|
|
134
133
|
super(convert_key(key))
|
135
134
|
end
|
136
135
|
|
137
|
-
def stringify_keys
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
def
|
136
|
+
def stringify_keys!
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def stringify_keys
|
141
|
+
dup
|
142
|
+
end
|
143
|
+
# undef :symbolize_keys!
|
144
|
+
def symbolize_keys
|
145
|
+
to_hash.symbolize_keys
|
146
|
+
end
|
147
|
+
|
148
|
+
def to_options!
|
149
|
+
self
|
150
|
+
end
|
142
151
|
|
143
152
|
# Convert to a Hash with String keys.
|
144
153
|
def to_hash
|
@@ -146,20 +155,20 @@ module Neo4j
|
|
146
155
|
end
|
147
156
|
|
148
157
|
protected
|
149
|
-
def convert_key(key)
|
150
|
-
key.kind_of?(Symbol) ? key.to_s : key
|
151
|
-
end
|
152
158
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
159
|
+
def convert_key(key)
|
160
|
+
key.is_a?(Symbol) ? key.to_s : key
|
161
|
+
end
|
162
|
+
|
163
|
+
def convert_value(value)
|
164
|
+
if value.is_a? Hash
|
165
|
+
value # .nested_under_indifferent_access
|
166
|
+
elsif value.is_a?(Array)
|
167
|
+
value.dup.replace(value.map { |e| convert_value(e) })
|
168
|
+
else
|
169
|
+
value
|
161
170
|
end
|
171
|
+
end
|
162
172
|
end
|
163
|
-
|
164
173
|
end
|
165
|
-
end
|
174
|
+
end
|
data/lib/neo4j-core/helpers.rb
CHANGED
@@ -1,24 +1,22 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Core
|
3
|
+
module ArgumentHelper
|
4
|
+
def self.session(args)
|
5
|
+
args.last.is_a?(Neo4j::Session) ? args.pop : Neo4j::Session.current!
|
6
|
+
end
|
7
7
|
end
|
8
|
-
end
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
module TxMethods
|
10
|
+
def tx_methods(*methods)
|
11
|
+
methods.each do |method|
|
12
|
+
tx_method = "#{method}_in_tx"
|
13
|
+
send(:alias_method, tx_method, method)
|
14
|
+
send(:define_method, method) do |*args, &block|
|
15
|
+
session = ArgumentHelper.session(args)
|
16
|
+
Neo4j::Transaction.run(session.auto_commit?) { send(tx_method, *args, &block) }
|
17
|
+
end
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
|
-
|
23
|
-
|
24
22
|
end
|
data/lib/neo4j-core/label.rb
CHANGED
data/lib/neo4j-core/query.rb
CHANGED
@@ -1,344 +1,347 @@
|
|
1
1
|
require 'neo4j-core/query_clauses'
|
2
2
|
require 'active_support/notifications'
|
3
3
|
|
4
|
-
module Neo4j
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# @method start *args
|
26
|
-
# START clause
|
27
|
-
# @return [Query]
|
28
|
-
|
29
|
-
# @method match *args
|
30
|
-
# MATCH clause
|
31
|
-
# @return [Query]
|
4
|
+
module Neo4j
|
5
|
+
module Core
|
6
|
+
# Allows for generation of cypher queries via ruby method calls (inspired by ActiveRecord / arel syntax)
|
7
|
+
#
|
8
|
+
# Can be used to express cypher queries in ruby nicely, or to more easily generate queries programatically.
|
9
|
+
#
|
10
|
+
# Also, queries can be passed around an application to progressively build a query across different concerns
|
11
|
+
#
|
12
|
+
# See also the following link for full cypher language documentation:
|
13
|
+
# http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html
|
14
|
+
class Query
|
15
|
+
include Neo4j::Core::QueryClauses
|
16
|
+
include Neo4j::Core::QueryFindInBatches
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
@session = options[:session] || Neo4j::Session.current
|
20
|
+
|
21
|
+
@options = options
|
22
|
+
@clauses = []
|
23
|
+
@_params = {}
|
24
|
+
end
|
32
25
|
|
33
|
-
|
34
|
-
|
35
|
-
|
26
|
+
# @method start *args
|
27
|
+
# START clause
|
28
|
+
# @return [Query]
|
36
29
|
|
37
|
-
|
38
|
-
|
39
|
-
|
30
|
+
# @method match *args
|
31
|
+
# MATCH clause
|
32
|
+
# @return [Query]
|
40
33
|
|
41
|
-
|
42
|
-
|
43
|
-
|
34
|
+
# @method optional_match *args
|
35
|
+
# OPTIONAL MATCH clause
|
36
|
+
# @return [Query]
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
# @method using *args
|
39
|
+
# USING clause
|
40
|
+
# @return [Query]
|
48
41
|
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
# @method where *args
|
43
|
+
# WHERE clause
|
44
|
+
# @return [Query]
|
52
45
|
|
53
|
-
|
54
|
-
|
55
|
-
|
46
|
+
# @method with *args
|
47
|
+
# WITH clause
|
48
|
+
# @return [Query]
|
56
49
|
|
57
|
-
|
58
|
-
|
59
|
-
|
50
|
+
# @method order *args
|
51
|
+
# ORDER BY clause
|
52
|
+
# @return [Query]
|
60
53
|
|
61
|
-
|
62
|
-
|
63
|
-
|
54
|
+
# @method limit *args
|
55
|
+
# LIMIT clause
|
56
|
+
# @return [Query]
|
64
57
|
|
65
|
-
|
66
|
-
|
67
|
-
|
58
|
+
# @method skip *args
|
59
|
+
# SKIP clause
|
60
|
+
# @return [Query]
|
68
61
|
|
69
|
-
|
70
|
-
|
71
|
-
|
62
|
+
# @method set *args
|
63
|
+
# SET clause
|
64
|
+
# @return [Query]
|
72
65
|
|
73
|
-
|
74
|
-
|
75
|
-
|
66
|
+
# @method remove *args
|
67
|
+
# REMOVE clause
|
68
|
+
# @return [Query]
|
76
69
|
|
77
|
-
|
78
|
-
|
79
|
-
|
70
|
+
# @method unwind *args
|
71
|
+
# UNWIND clause
|
72
|
+
# @return [Query]
|
80
73
|
|
81
|
-
|
82
|
-
|
83
|
-
|
74
|
+
# @method return *args
|
75
|
+
# RETURN clause
|
76
|
+
# @return [Query]
|
84
77
|
|
85
|
-
|
86
|
-
|
87
|
-
|
78
|
+
# @method create *args
|
79
|
+
# CREATE clause
|
80
|
+
# @return [Query]
|
88
81
|
|
89
|
-
|
90
|
-
|
91
|
-
|
82
|
+
# @method create_unique *args
|
83
|
+
# CREATE UNIQUE clause
|
84
|
+
# @return [Query]
|
92
85
|
|
93
|
-
|
94
|
-
|
95
|
-
|
86
|
+
# @method merge *args
|
87
|
+
# MERGE clause
|
88
|
+
# @return [Query]
|
96
89
|
|
97
|
-
|
98
|
-
|
99
|
-
|
90
|
+
# @method on_create_set *args
|
91
|
+
# ON CREATE SET clause
|
92
|
+
# @return [Query]
|
100
93
|
|
101
|
-
|
94
|
+
# @method on_match_set *args
|
95
|
+
# ON MATCH SET clause
|
96
|
+
# @return [Query]
|
102
97
|
|
103
|
-
|
98
|
+
# @method delete *args
|
99
|
+
# DELETE clause
|
100
|
+
# @return [Query]
|
104
101
|
|
105
|
-
|
106
|
-
clause_class = CLAUSES[i]
|
102
|
+
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)
|
107
103
|
|
108
|
-
|
109
|
-
def #{clause}(*args)
|
110
|
-
build_deeper_query(#{clause_class}, args)
|
111
|
-
end}, __FILE__, __LINE__)
|
112
|
-
end
|
104
|
+
CLAUSES = METHODS.map { |method| const_get(method.split('_').map(&:capitalize).join + 'Clause') }
|
113
105
|
|
114
|
-
|
115
|
-
|
106
|
+
METHODS.each_with_index do |clause, i|
|
107
|
+
clause_class = CLAUSES[i]
|
116
108
|
|
117
|
-
|
118
|
-
|
119
|
-
|
109
|
+
module_eval(%{
|
110
|
+
def #{clause}(*args)
|
111
|
+
build_deeper_query(#{clause_class}, args)
|
112
|
+
end}, __FILE__, __LINE__)
|
113
|
+
end
|
120
114
|
|
121
|
-
|
122
|
-
|
123
|
-
end
|
115
|
+
alias_method :offset, :skip
|
116
|
+
alias_method :order_by, :order
|
124
117
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
# Query.new.match(n: :Person).set_props(n: {age: 19})
|
129
|
-
def set_props(*args)
|
130
|
-
build_deeper_query(SetClause, args, set_props: true)
|
131
|
-
end
|
118
|
+
# Clears out previous order clauses and allows only for those specified by args
|
119
|
+
def reorder(*args)
|
120
|
+
query = copy
|
132
121
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
# Query.new.match(q: Person).match('r:Car').break.match('(p: Person)-->q')
|
137
|
-
def break
|
138
|
-
build_deeper_query(nil)
|
139
|
-
end
|
122
|
+
query.remove_clause_class(OrderClause)
|
123
|
+
query.order(*args)
|
124
|
+
end
|
140
125
|
|
141
|
-
|
142
|
-
|
143
|
-
#
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
def params(args)
|
149
|
-
@_params = @_params.merge(args)
|
126
|
+
# Works the same as the #set method, but when given a nested array it will set properties rather than setting entire objects
|
127
|
+
# @example
|
128
|
+
# # Creates a query representing the cypher: MATCH (n:Person) SET n.age = 19
|
129
|
+
# Query.new.match(n: :Person).set_props(n: {age: 19})
|
130
|
+
def set_props(*args)
|
131
|
+
build_deeper_query(SetClause, args, set_props: true)
|
132
|
+
end
|
150
133
|
|
151
|
-
|
152
|
-
|
134
|
+
# 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
|
135
|
+
# @example
|
136
|
+
# # Creates a query representing the cypher: MATCH (q:Person), r:Car MATCH (p: Person)-->q
|
137
|
+
# Query.new.match(q: Person).match('r:Car').break.match('(p: Person)-->q')
|
138
|
+
def break
|
139
|
+
build_deeper_query(nil)
|
140
|
+
end
|
153
141
|
|
154
|
-
|
155
|
-
|
156
|
-
cypher
|
157
|
-
|
158
|
-
|
142
|
+
# Allows for the specification of values for params specified in query
|
143
|
+
# @example
|
144
|
+
# # Creates a query representing the cypher: MATCH (q: Person {id: {id}})
|
145
|
+
# # Calls to params don't affect the cypher query generated, but the params will be
|
146
|
+
# # Passed down when the query is made
|
147
|
+
# Query.new.match('(q: Person {id: {id}})').params(id: 12)
|
148
|
+
#
|
149
|
+
def params(args)
|
150
|
+
@_params = @_params.merge(args)
|
151
|
+
|
152
|
+
self
|
159
153
|
end
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
154
|
+
|
155
|
+
def response
|
156
|
+
return @response if @response
|
157
|
+
cypher = to_cypher
|
158
|
+
@response = ActiveSupport::Notifications.instrument('neo4j.cypher_query', context: @options[:context] || 'CYPHER', cypher: cypher, params: merge_params) do
|
159
|
+
@session._query(cypher, merge_params)
|
160
|
+
end
|
161
|
+
if !response.respond_to?(:error?) || !response.error?
|
162
|
+
response
|
163
|
+
else
|
164
|
+
response.raise_cypher_error
|
165
|
+
end
|
164
166
|
end
|
165
|
-
end
|
166
167
|
|
167
|
-
|
168
|
+
include Enumerable
|
168
169
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
170
|
+
def each
|
171
|
+
response = self.response
|
172
|
+
if response.is_a?(Neo4j::Server::CypherResponse)
|
173
|
+
response.to_node_enumeration
|
174
|
+
else
|
175
|
+
Neo4j::Embedded::ResultWrapper.new(response, to_cypher)
|
176
|
+
end.each { |object| yield object }
|
177
|
+
end
|
177
178
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
179
|
+
# @method to_a
|
180
|
+
# 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
|
181
|
+
# @return [Array]
|
182
|
+
# @raise [Neo4j::Server::CypherResponse::ResponseError] Raises errors from neo4j server
|
182
183
|
|
183
184
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
185
|
+
# Executes a query without returning the result
|
186
|
+
# @return [Boolean] true if successful
|
187
|
+
# @raise [Neo4j::Server::CypherResponse::ResponseError] Raises errors from neo4j server
|
188
|
+
def exec
|
189
|
+
response
|
189
190
|
|
190
|
-
|
191
|
-
|
191
|
+
true
|
192
|
+
end
|
192
193
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
194
|
+
# Return the specified columns as an array.
|
195
|
+
# If one column is specified, a one-dimensional array is returned with the values of that column
|
196
|
+
# If two columns are specified, a n-dimensional array is returned with the values of those columns
|
197
|
+
#
|
198
|
+
# @example
|
199
|
+
# Query.new.match(n: :Person).return(p: :name}.pluck(p: :name) # => Array of names
|
200
|
+
# @example
|
201
|
+
# Query.new.match(n: :Person).return(p: :name}.pluck('p, DISTINCT p.name') # => Array of [node, name] pairs
|
202
|
+
#
|
203
|
+
def pluck(*columns)
|
204
|
+
query = return_query(columns)
|
205
|
+
columns = query.response.columns
|
206
|
+
|
207
|
+
case columns.size
|
208
|
+
when 0
|
209
|
+
fail ArgumentError, 'No columns specified for Query#pluck'
|
210
|
+
when 1
|
211
|
+
column = columns[0]
|
212
|
+
query.map { |row| row[column] }
|
213
|
+
else
|
214
|
+
query.map do |row|
|
215
|
+
columns.map do |column|
|
216
|
+
row[column]
|
217
|
+
end
|
216
218
|
end
|
217
219
|
end
|
218
220
|
end
|
219
|
-
end
|
220
221
|
|
221
|
-
|
222
|
-
|
223
|
-
|
222
|
+
def return_query(columns)
|
223
|
+
query = copy
|
224
|
+
query.remove_clause_class(ReturnClause)
|
224
225
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
226
|
+
columns = columns.map do |column_definition|
|
227
|
+
if column_definition.is_a?(Hash)
|
228
|
+
column_definition.map { |k, v| "#{k}.#{v}" }
|
229
|
+
else
|
230
|
+
column_definition
|
231
|
+
end
|
232
|
+
end.flatten.map(&:to_sym)
|
232
233
|
|
233
|
-
|
234
|
-
|
234
|
+
query.return(columns)
|
235
|
+
end
|
235
236
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
237
|
+
# Returns a CYPHER query string from the object query representation
|
238
|
+
# @example
|
239
|
+
# Query.new.match(p: :Person).where(p: {age: 30}) # => "MATCH (p:Person) WHERE p.age = 30
|
240
|
+
#
|
241
|
+
# @return [String] Resulting cypher query string
|
242
|
+
def to_cypher
|
243
|
+
cypher_string = partitioned_clauses.map do |clauses|
|
244
|
+
clauses_by_class = clauses.group_by(&:class)
|
244
245
|
|
245
|
-
|
246
|
-
|
246
|
+
cypher_parts = CLAUSES.map do |clause_class|
|
247
|
+
clauses = clauses_by_class[clause_class]
|
247
248
|
|
248
|
-
|
249
|
-
|
249
|
+
clause_class.to_cypher(clauses) if clauses
|
250
|
+
end
|
250
251
|
|
251
|
-
|
252
|
-
|
253
|
-
|
252
|
+
cypher_string = cypher_parts.compact.join(' ')
|
253
|
+
cypher_string.strip
|
254
|
+
end.join ' '
|
254
255
|
|
255
|
-
|
256
|
-
|
257
|
-
|
256
|
+
cypher_string = "CYPHER #{@options[:parser]} #{cypher_string}" if @options[:parser]
|
257
|
+
cypher_string.strip
|
258
|
+
end
|
258
259
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
260
|
+
# Returns a CYPHER query specifying the union of the callee object's query and the argument's query
|
261
|
+
#
|
262
|
+
# @example
|
263
|
+
# # Generates cypher: MATCH (n:Person) UNION MATCH (o:Person) WHERE o.age = 10
|
264
|
+
# q = Neo4j::Core::Query.new.match(o: :Person).where(o: {age: 10})
|
265
|
+
# result = Neo4j::Core::Query.new.match(n: :Person).union_cypher(q)
|
266
|
+
#
|
267
|
+
# @param other [Query] Second half of UNION
|
268
|
+
# @param options [Hash] Specify {all: true} to use UNION ALL
|
269
|
+
# @return [String] Resulting UNION cypher query string
|
270
|
+
def union_cypher(other, options = {})
|
271
|
+
"#{to_cypher} UNION#{options[:all] ? ' ALL' : ''} #{other.to_cypher}"
|
272
|
+
end
|
272
273
|
|
273
|
-
|
274
|
-
|
274
|
+
def &(other)
|
275
|
+
fail "Sessions don't match!" if @session != other.session
|
275
276
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
277
|
+
self.class.new(session: @session).tap do |new_query|
|
278
|
+
new_query.options = options.merge(other.options)
|
279
|
+
new_query.clauses = clauses + other.clauses
|
280
|
+
end.params(other._params)
|
281
|
+
end
|
281
282
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
283
|
+
MEMOIZED_INSTANCE_VARIABLES = [:response, :merge_params]
|
284
|
+
def copy
|
285
|
+
dup.tap do |query|
|
286
|
+
MEMOIZED_INSTANCE_VARIABLES.each do |var|
|
287
|
+
query.instance_variable_set("@#{var}", nil)
|
288
|
+
end
|
287
289
|
end
|
288
290
|
end
|
289
|
-
end
|
290
291
|
|
291
|
-
|
292
|
-
attr_accessor :session, :options, :clauses, :_params
|
292
|
+
protected
|
293
293
|
|
294
|
-
|
295
|
-
@clauses += clauses
|
296
|
-
end
|
294
|
+
attr_accessor :session, :options, :clauses, :_params
|
297
295
|
|
298
|
-
|
299
|
-
|
300
|
-
clause.is_a?(clause_class)
|
296
|
+
def add_clauses(clauses)
|
297
|
+
@clauses += clauses
|
301
298
|
end
|
302
|
-
end
|
303
|
-
private
|
304
299
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
300
|
+
def remove_clause_class(clause_class)
|
301
|
+
@clauses = @clauses.reject do |clause|
|
302
|
+
clause.is_a?(clause_class)
|
303
|
+
end
|
309
304
|
end
|
310
|
-
end
|
311
305
|
|
312
|
-
|
313
|
-
self.copy.tap do |new_query|
|
314
|
-
new_query.add_clauses [nil]
|
315
|
-
end
|
316
|
-
end
|
306
|
+
private
|
317
307
|
|
318
|
-
|
319
|
-
|
308
|
+
def build_deeper_query(clause_class, args = {}, options = {})
|
309
|
+
copy.tap do |new_query|
|
310
|
+
new_query.add_clauses [nil] if [nil, WithClause].include?(clause_class)
|
311
|
+
new_query.add_clauses clause_class.from_args(args, options) if clause_class
|
312
|
+
end
|
313
|
+
end
|
320
314
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
else
|
325
|
-
partitioning.last << clause
|
315
|
+
def break_deeper_query
|
316
|
+
copy.tap do |new_query|
|
317
|
+
new_query.add_clauses [nil]
|
326
318
|
end
|
327
319
|
end
|
328
320
|
|
329
|
-
|
330
|
-
|
321
|
+
def partitioned_clauses
|
322
|
+
partitioning = [[]]
|
331
323
|
|
332
|
-
|
333
|
-
|
334
|
-
|
324
|
+
@clauses.each do |clause|
|
325
|
+
if clause.nil? && partitioning.last != []
|
326
|
+
partitioning << []
|
327
|
+
else
|
328
|
+
partitioning.last << clause
|
329
|
+
end
|
330
|
+
end
|
335
331
|
|
336
|
-
|
337
|
-
passthrough_classes = [String, Numeric, Array, Regexp]
|
338
|
-
params.each do |key, value|
|
339
|
-
params[key] = value.to_s if not passthrough_classes.any? {|klass| value.is_a?(klass) }
|
332
|
+
partitioning
|
340
333
|
end
|
341
|
-
end
|
342
334
|
|
335
|
+
def merge_params
|
336
|
+
@merge_params ||= @clauses.compact.inject(@_params) { |params, clause| params.merge(clause.params) }
|
337
|
+
end
|
338
|
+
|
339
|
+
def sanitize_params(params)
|
340
|
+
passthrough_classes = [String, Numeric, Array, Regexp]
|
341
|
+
params.each do |key, value|
|
342
|
+
params[key] = value.to_s if not passthrough_classes.any? { |klass| value.is_a?(klass) }
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
343
346
|
end
|
344
347
|
end
|