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
@@ -1,22 +1,23 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Embedded
|
3
|
+
# A Utility class for translating Ruby object to Neo4j Java types
|
4
|
+
# @private
|
5
|
+
module ToJava
|
6
|
+
def type_to_java(type)
|
7
|
+
Java::OrgNeo4jGraphdb::DynamicRelationshipType.withName(type.to_s)
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
+
module_function :type_to_java
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
def types_to_java(types)
|
13
|
+
types.inject([]) { |result, type| result << type_to_java(type) }.to_java(Java::OrgNeo4jGraphdb::RelationshipType)
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
+
module_function :types_to_java
|
16
17
|
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
def dir_from_java(dir)
|
20
|
+
case dir
|
20
21
|
when Java::OrgNeo4jGraphdb::Direction::OUTGOING then
|
21
22
|
:outgoing
|
22
23
|
when Java::OrgNeo4jGraphdb::Direction::BOTH then
|
@@ -24,14 +25,14 @@ module Neo4j::Embedded
|
|
24
25
|
when Java::OrgNeo4jGraphdb::Direction::INCOMING then
|
25
26
|
:incoming
|
26
27
|
else
|
27
|
-
|
28
|
+
fail "unknown direction '#{dir} / #{dir.class}'"
|
29
|
+
end
|
28
30
|
end
|
29
|
-
end
|
30
31
|
|
31
|
-
|
32
|
+
module_function :dir_from_java
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
def dir_to_java(dir)
|
35
|
+
case dir
|
35
36
|
when :outgoing then
|
36
37
|
Java::OrgNeo4jGraphdb::Direction::OUTGOING
|
37
38
|
when :both then
|
@@ -39,11 +40,11 @@ module Neo4j::Embedded
|
|
39
40
|
when :incoming then
|
40
41
|
Java::OrgNeo4jGraphdb::Direction::INCOMING
|
41
42
|
else
|
42
|
-
|
43
|
+
fail "unknown direction '#{dir}', expects argument: outgoing, :incoming or :both"
|
44
|
+
end
|
43
45
|
end
|
44
|
-
end
|
45
|
-
|
46
|
-
module_function :dir_to_java
|
47
46
|
|
47
|
+
module_function :dir_to_java
|
48
|
+
end
|
48
49
|
end
|
49
50
|
end
|
data/lib/neo4j-server.rb
CHANGED
@@ -1,123 +1,125 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
class
|
5
|
-
|
6
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Server
|
3
|
+
# Neo4j 2.2 has an authentication layer. This class provides methods for interacting with it.
|
4
|
+
class CypherAuthentication
|
5
|
+
class InvalidPasswordError < RuntimeError; end
|
6
|
+
class PasswordChangeRequiredError < RuntimeError; end
|
7
|
+
class MissingCredentialsError < RuntimeError; end
|
7
8
|
|
8
|
-
|
9
|
+
attr_reader :connection, :url, :params, :token
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
11
|
+
# @param [String] url_string The server address with protocol and port.
|
12
|
+
# @param [Faraday::Connection] session_connection A Faraday::Connection object. This is either an existing object, likely the
|
13
|
+
# same object used by the server for data, or a new one created specifically for auth tasks.
|
14
|
+
# @param [Hash] params_hash Faraday connection options. In particularly, we're looking for basic_auth creds.
|
15
|
+
def initialize(url_string, session_connection = new_connection, params_hash = {})
|
16
|
+
@url = url_string
|
17
|
+
@connection = session_connection
|
18
|
+
@params = params_hash
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
# Set the username and password used to communicate with the server.
|
22
|
+
def basic_auth(username, password)
|
23
|
+
params[:basic_auth] ||= {}
|
24
|
+
params[:basic_auth][:username] = username
|
25
|
+
params[:basic_auth][:password] = password
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
# POSTs to the password change endpoint of the API. Does not invalidate tokens.
|
29
|
+
# @param [String] old_password The current password.
|
30
|
+
# @param [String] new_password The password you want to use.
|
31
|
+
# @return [Hash] The response from the server.
|
32
|
+
def change_password(old_password, new_password)
|
33
|
+
connection.post("#{url}/user/neo4j/password", 'password' => old_password, 'new_password' => new_password).body
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
36
|
+
# Uses the given username and password to obtain a token, then adds the token to the connection's parameters.
|
37
|
+
# @return [String] An access token provided by the server.
|
38
|
+
def authenticate
|
39
|
+
auth_response = auth_connection("#{url}/authentication")
|
40
|
+
auth_hash = if auth_response.body.empty?
|
41
|
+
nil
|
42
|
+
elsif auth_response.body.is_a?(String)
|
43
|
+
JSON.parse(auth_response.body)['errors'][0]['code'] == 'Neo.ClientError.Security.AuthorizationFailed' ? auth_attempt : nil
|
44
|
+
else
|
45
|
+
auth_response
|
46
|
+
end
|
47
|
+
return nil if auth_hash.nil?
|
48
|
+
add_auth_headers(token_or_error(auth_hash))
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
# Invalidates the existing token, which will invalidate all conncetions using this token, applies for a new token, adds this into
|
52
|
+
# the connection headers.
|
53
|
+
# @param [String] password The current server password.
|
54
|
+
def reauthenticate(password)
|
55
|
+
invalidate_token(password)
|
56
|
+
add_auth_headers(token_or_error(auth_attempt))
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
59
|
+
# Requests a token from the authentication endpoint using the given username and password.
|
60
|
+
# @return [Faraday::Response] The server's response, to be interpreted.
|
61
|
+
def auth_attempt
|
62
|
+
begin
|
63
|
+
user = params[:basic_auth][:username]
|
64
|
+
pass = params[:basic_auth][:password]
|
65
|
+
rescue NoMethodError
|
66
|
+
raise MissingCredentialsError, 'Neo4j authentication is enabled, username/password are required but missing'
|
67
|
+
end
|
68
|
+
connection.post("#{url}/authentication", 'username' => user, 'password' => pass)
|
66
69
|
end
|
67
|
-
connection.post("#{url}/authentication", { 'username' => user, 'password' => pass })
|
68
|
-
end
|
69
70
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
71
|
+
# Takes a response object from the server and returns a token or fails with an error.
|
72
|
+
# TODO: more error states!
|
73
|
+
# @param [Farday::Response] auth_response The response after attempting authentication
|
74
|
+
# @return [String] An authentication token.
|
75
|
+
def token_or_error(auth_response)
|
76
|
+
begin
|
77
|
+
fail PasswordChangeRequiredError, "Server requires a password change, please visit #{url}" if auth_response.body['password_change_required']
|
78
|
+
fail InvalidPasswordError, "Neo4j server responded with: #{auth_response.body['errors'][0]['message']}" if auth_response.status.to_i == 422
|
79
|
+
rescue NoMethodError
|
80
|
+
raise 'Unexpected auth response, please open an issue at https://github.com/neo4jrb/neo4j-core/issues'
|
81
|
+
end
|
82
|
+
auth_response.body['authorization_token']
|
80
83
|
end
|
81
|
-
auth_response.body['authorization_token']
|
82
|
-
end
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
# Invalidates tokens as described at http://neo4j.com/docs/snapshot/rest-api-security.html#rest-api-invalidating-the-authorization-token
|
86
|
+
# @param [String] current_password The current password used to connect to the database
|
87
|
+
def invalidate_token(current_password)
|
88
|
+
connection.post("#{url}/user/neo4j/authorization_token", 'password' => current_password).body
|
89
|
+
end
|
89
90
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
# Stores an authentication token in the properly-formatted header.
|
92
|
+
# This does not do any checking that what it has been given is a token. Whatever param is given will be base64 encoded and used as the header.
|
93
|
+
# @param [String] token The authentication token provided by the database.
|
94
|
+
def add_auth_headers(token)
|
95
|
+
@token = token
|
96
|
+
connection.headers['Authorization'] = "Basic realm=\"Neo4j\" #{token_hash(token)}"
|
97
|
+
end
|
97
98
|
|
98
|
-
|
99
|
+
private
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
# Makes testing easier, we can stub this method to simulate different responses
|
102
|
+
def auth_connection(url)
|
103
|
+
connection.get(url)
|
104
|
+
end
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
106
|
+
def self.new_connection
|
107
|
+
conn = Faraday.new do |b|
|
108
|
+
b.request :json
|
109
|
+
b.response :json, content_type: 'application/json'
|
110
|
+
b.use Faraday::Adapter::NetHttpPersistent
|
111
|
+
end
|
112
|
+
conn.headers = {'Content-Type' => 'application/json'}
|
113
|
+
conn
|
110
114
|
end
|
111
|
-
conn.headers = { 'Content-Type' => 'application/json' }
|
112
|
-
conn
|
113
|
-
end
|
114
115
|
|
115
|
-
|
116
|
-
|
117
|
-
|
116
|
+
def new_connection
|
117
|
+
self.class.new_connection
|
118
|
+
end
|
118
119
|
|
119
|
-
|
120
|
-
|
120
|
+
def token_hash(token)
|
121
|
+
::Base64.strict_encode64(":#{token}")
|
122
|
+
end
|
121
123
|
end
|
122
124
|
end
|
123
|
-
end
|
125
|
+
end
|
@@ -1,32 +1,34 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Server
|
3
|
+
class CypherLabel < Neo4j::Label
|
4
|
+
extend Forwardable
|
5
|
+
def_delegator :@session, :query_cypher_for
|
6
|
+
attr_reader :name
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def initialize(session, name)
|
9
|
+
@name = name
|
10
|
+
@session = session
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def create_index(*properties)
|
14
|
+
response = @session._query("CREATE INDEX ON :`#{@name}`(#{properties.join(',')})")
|
15
|
+
response.raise_error if response.error?
|
16
|
+
end
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def drop_index(*properties)
|
19
|
+
properties.each do |property|
|
20
|
+
response = @session._query("DROP INDEX ON :`#{@name}`(#{property})")
|
21
|
+
response.raise_error if response.error? && !response.error_msg.match(/No such INDEX ON/)
|
22
|
+
end
|
21
23
|
end
|
22
|
-
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def indexes
|
26
|
+
@session.indexes(@name)
|
27
|
+
end
|
27
28
|
|
28
|
-
|
29
|
-
|
29
|
+
def uniqueness_constraints
|
30
|
+
@session.uniqueness_constraints(@name)
|
31
|
+
end
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -1,218 +1,221 @@
|
|
1
|
-
module Neo4j
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
1
|
+
module Neo4j
|
2
|
+
module Server
|
3
|
+
class CypherNode < Neo4j::Node
|
4
|
+
include Neo4j::Server::Resource
|
5
|
+
include Neo4j::Core::CypherTranslator
|
6
|
+
include Neo4j::Core::ActiveEntity
|
7
|
+
|
8
|
+
def initialize(session, value)
|
9
|
+
@session = session
|
10
|
+
|
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
|
16
|
+
else
|
17
|
+
value
|
18
|
+
end
|
17
19
|
end
|
18
|
-
end
|
19
20
|
|
20
|
-
|
21
|
-
@id
|
22
|
-
end
|
21
|
+
attr_reader :neo_id
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
def inspect
|
24
|
+
"CypherNode #{neo_id} (#{object_id})"
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
27
|
+
# TODO, needed by neo4j-cypher
|
28
|
+
def _java_node
|
29
|
+
self
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
32
|
+
# (see Neo4j::Node#create_rel)
|
33
|
+
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
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
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)"
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
# (see Neo4j::Node#props)
|
46
|
+
def props
|
47
|
+
if @props
|
48
|
+
@props
|
49
|
+
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] }]
|
52
|
+
end
|
51
53
|
end
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
55
|
+
def refresh
|
56
|
+
@props = nil
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
59
|
+
# (see Neo4j::Node#remove_property)
|
60
|
+
def remove_property(key)
|
61
|
+
refresh
|
62
|
+
@session._query_or_fail("#{match_start} REMOVE n.`#{key}`", false, neo_id: neo_id)
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
65
|
+
# (see Neo4j::Node#set_property)
|
66
|
+
def set_property(key, value)
|
67
|
+
refresh
|
68
|
+
@session._query_or_fail("#{match_start} SET n.`#{key}` = { value }", false, value: value, neo_id: neo_id)
|
69
|
+
value
|
70
|
+
end
|
70
71
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
72
|
+
# (see Neo4j::Node#props=)
|
73
|
+
def props=(properties)
|
74
|
+
refresh
|
75
|
+
@session._query_or_fail("#{match_start} SET n = { props }", false, props: properties, neo_id: neo_id)
|
76
|
+
properties
|
77
|
+
end
|
77
78
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
79
|
+
def remove_properties(properties)
|
80
|
+
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)
|
85
|
+
end
|
85
86
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
87
|
+
# (see Neo4j::Node#update_props)
|
88
|
+
def update_props(properties)
|
89
|
+
refresh
|
90
|
+
return if properties.empty?
|
91
|
+
|
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)
|
98
|
+
properties
|
99
|
+
end
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
# (see Neo4j::Node#get_property)
|
102
|
+
def get_property(key)
|
103
|
+
@props ? @props[key.to_sym] : @session._query_or_fail("#{match_start} RETURN n.`#{key}`", true, neo_id: neo_id)
|
104
|
+
end
|
104
105
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
106
|
+
# (see Neo4j::Node#labels)
|
107
|
+
def labels
|
108
|
+
@labels ||= @session._query_or_fail("#{match_start} RETURN labels(n) as labels", true, neo_id: neo_id).map!(&:to_sym)
|
109
|
+
end
|
110
110
|
|
111
|
-
|
112
|
-
|
113
|
-
|
111
|
+
def _cypher_label_list(labels_list)
|
112
|
+
':' + labels_list.map { |label| "`#{label}`" }.join(':')
|
113
|
+
end
|
114
114
|
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
def add_label(*new_labels)
|
116
|
+
@session._query_or_fail("#{match_start} SET n #{_cypher_label_list(new_labels)}", false, neo_id: neo_id)
|
117
|
+
new_labels.each { |label| labels << label }
|
118
|
+
end
|
118
119
|
|
119
|
-
|
120
|
-
|
121
|
-
|
120
|
+
def remove_label(*target_labels)
|
121
|
+
@session._query_or_fail("#{match_start} REMOVE n #{_cypher_label_list(target_labels)}", false, neo_id: neo_id)
|
122
|
+
target_labels.each { |label| labels.delete(label) } unless labels.nil?
|
123
|
+
end
|
122
124
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
125
|
+
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)
|
128
|
+
end
|
127
129
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
130
|
+
# (see Neo4j::Node#del)
|
131
|
+
def del
|
132
|
+
@session._query_or_fail("#{match_start} OPTIONAL MATCH n-[r]-() DELETE n, r", false, neo_id: neo_id)
|
133
|
+
end
|
132
134
|
|
133
|
-
|
134
|
-
|
135
|
+
alias_method :delete, :del
|
136
|
+
alias_method :destroy, :del
|
135
137
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
138
|
+
# (see Neo4j::Node#exist?)
|
139
|
+
def exist?
|
140
|
+
@session._query("#{match_start} RETURN ID(n)", neo_id: neo_id).data.empty? ? false : true
|
141
|
+
end
|
140
142
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
143
|
+
# (see Neo4j::Node#node)
|
144
|
+
def node(match = {})
|
145
|
+
ensure_single_relationship { match(CypherNode, 'p as result LIMIT 2', match) }
|
146
|
+
end
|
145
147
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
148
|
+
# (see Neo4j::Node#rel)
|
149
|
+
def rel(match = {})
|
150
|
+
ensure_single_relationship { match(CypherRelationship, 'r as result LIMIT 2', match) }
|
151
|
+
end
|
150
152
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
153
|
+
# (see Neo4j::Node#rel?)
|
154
|
+
def rel?(match = {})
|
155
|
+
result = match(CypherRelationship, 'r as result', match)
|
156
|
+
!!result.first
|
157
|
+
end
|
156
158
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
159
|
+
# (see Neo4j::Node#nodes)
|
160
|
+
def nodes(match = {})
|
161
|
+
match(CypherNode, 'p as result', match)
|
162
|
+
end
|
161
163
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
164
|
+
# (see Neo4j::Node#rels)
|
165
|
+
def rels(match = {dir: :both})
|
166
|
+
match(CypherRelationship, 'r as result', match)
|
167
|
+
end
|
166
168
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
169
|
+
# @private
|
170
|
+
def match(clazz, returns, match = {})
|
171
|
+
to_dir = {outgoing: ->(rel) { "-#{rel}->" },
|
172
|
+
incoming: ->(rel) { "<-#{rel}-" },
|
173
|
+
both: ->(rel) { "-#{rel}-" }}
|
174
|
+
|
175
|
+
cypher_rel = match[:type] ? "[r:`#{match[:type]}`]" : '[r]'
|
176
|
+
between_id = match[:between] ? "MATCH (p) WHERE ID(p) = #{match[:between].neo_id}" : ''
|
177
|
+
dir_func = to_dir[match[:dir] || :both]
|
178
|
+
cypher = "#{match_start} #{between_id} MATCH (n)#{dir_func.call(cypher_rel)}(p) RETURN #{returns}"
|
179
|
+
r = @session._query(cypher, neo_id: neo_id)
|
180
|
+
r.raise_error if r.error?
|
181
|
+
_map_result(r)
|
182
|
+
end
|
181
183
|
|
182
|
-
|
183
|
-
|
184
|
-
|
184
|
+
def _map_result(r)
|
185
|
+
r.to_node_enumeration.map(&:result)
|
186
|
+
end
|
185
187
|
|
186
|
-
|
188
|
+
private
|
187
189
|
|
188
|
-
|
189
|
-
|
190
|
-
|
190
|
+
def cypher_properties(properties_to_set)
|
191
|
+
properties_to_set.map! { |k| "n.`#{k}` = {`#{k}`}" }.join(',')
|
192
|
+
end
|
191
193
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
194
|
+
def remove_labels_if_needed
|
195
|
+
if labels.empty?
|
196
|
+
''
|
197
|
+
else
|
198
|
+
" REMOVE n #{_cypher_label_list(labels)}"
|
199
|
+
end
|
197
200
|
end
|
198
|
-
end
|
199
201
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
202
|
+
def set_labels_if_needed(label_names)
|
203
|
+
if label_names.empty?
|
204
|
+
''
|
205
|
+
else
|
206
|
+
" SET n #{_cypher_label_list(label_names.map(&:to_sym).uniq)}"
|
207
|
+
end
|
205
208
|
end
|
206
|
-
end
|
207
209
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
210
|
+
def ensure_single_relationship(&block)
|
211
|
+
result = yield
|
212
|
+
fail "Expected to only find one relationship from node #{neo_id} matching #{match.inspect} but found #{result.count}" if result.count > 1
|
213
|
+
result.first
|
214
|
+
end
|
213
215
|
|
214
|
-
|
215
|
-
|
216
|
+
def match_start(identifier = 'n')
|
217
|
+
"MATCH (#{identifier}) WHERE ID(#{identifier}) = {neo_id}"
|
218
|
+
end
|
216
219
|
end
|
217
220
|
end
|
218
221
|
end
|