neography 1.0.10 → 1.0.11
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -1
- data/README.md +1 -0
- data/lib/neography/connection.rb +45 -34
- data/lib/neography/node.rb +1 -1
- data/lib/neography/path_traverser.rb +9 -4
- data/lib/neography/property_container.rb +16 -4
- data/lib/neography/rest.rb +86 -2
- data/lib/neography/rest/batch.rb +4 -12
- data/lib/neography/rest/cypher.rb +12 -3
- data/lib/neography/rest/helpers.rb +12 -0
- data/lib/neography/rest/node_labels.rb +60 -0
- data/lib/neography/rest/node_relationships.rb +0 -11
- data/lib/neography/rest/other_node_relationships.rb +1 -12
- data/lib/neography/rest/relationship_types.rb +18 -0
- data/lib/neography/rest/schema_indexes.rb +34 -0
- data/lib/neography/rest/transactions.rb +83 -0
- data/lib/neography/tasks.rb +1 -1
- data/lib/neography/version.rb +1 -1
- data/spec/integration/node_spec.rb +13 -0
- data/spec/integration/rest_batch_spec.rb +11 -15
- data/spec/integration/rest_batch_streaming_spec.rb +2 -2
- data/spec/integration/rest_gremlin_fail_spec.rb +4 -4
- data/spec/integration/rest_header_spec.rb +1 -1
- data/spec/integration/rest_labels_spec.rb +131 -0
- data/spec/integration/rest_plugin_spec.rb +32 -3
- data/spec/integration/rest_relationship_types_spec.rb +18 -0
- data/spec/integration/rest_schema_index_spec.rb +32 -0
- data/spec/integration/rest_transaction_spec.rb +100 -0
- data/spec/spec_helper.rb +8 -1
- data/spec/unit/connection_spec.rb +1 -1
- data/spec/unit/node_spec.rb +1 -1
- data/spec/unit/rest/batch_spec.rb +21 -21
- data/spec/unit/rest/labels_spec.rb +73 -0
- data/spec/unit/rest/relationship_types_spec.rb +16 -0
- data/spec/unit/rest/schema_index_spec.rb +31 -0
- data/spec/unit/rest/transactions_spec.rb +44 -0
- metadata +22 -2
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -99,6 +99,7 @@ Some of this functionality is shown here, but all of it is explained in the foll
|
|
99
99
|
* [Node relationships](https://github.com/maxdemarzi/neography/wiki/Node-relationships) - Create and get relationships between nodes.
|
100
100
|
* [Relationship](https://github.com/maxdemarzi/neography/wiki/Relationships) - Get and delete relationships.
|
101
101
|
* [Relationship properties](https://github.com/maxdemarzi/neography/wiki/Relationship-properties) - Create, get and delete relationship properties.
|
102
|
+
* [Relationship types](https://github.com/maxdemarzi/neography/wiki/Relationship-types) - List relationship types.
|
102
103
|
* [Node indexes](https://github.com/maxdemarzi/neography/wiki/Node-indexes) - List and create node indexes. Add, remove, get and search nodes in indexes.
|
103
104
|
* [Relationship indexes](https://github.com/maxdemarzi/neography/wiki/Relationship-indexes) - List and create relationships indexes. Add, remove, get and search relationships in indexes.
|
104
105
|
* [Auto indexes](https://github.com/maxdemarzi/neography/wiki/Auto-indexes) - Get, set and remove auto indexes.
|
data/lib/neography/connection.rb
CHANGED
@@ -16,6 +16,7 @@ module Neography
|
|
16
16
|
save_local_configuration(config)
|
17
17
|
@client = HTTPClient.new
|
18
18
|
@client.send_timeout = 1200 # 10 minutes
|
19
|
+
@client.receive_timeout = 1200
|
19
20
|
end
|
20
21
|
|
21
22
|
def configure(protocol, server, port, directory)
|
@@ -30,8 +31,9 @@ module Neography
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def merge_options(options)
|
33
|
-
merged_options = options.merge!(@authentication)
|
34
|
+
merged_options = options.merge!(@authentication)
|
34
35
|
merged_options[:headers].merge!(@user_agent) if merged_options[:headers]
|
36
|
+
merged_options[:headers].merge!('X-Stream' => true) if merged_options[:headers]
|
35
37
|
merged_options
|
36
38
|
end
|
37
39
|
|
@@ -45,23 +47,6 @@ module Neography
|
|
45
47
|
evaluate_response(@client.post(configuration + path, merge_options(options)[:body], merge_options(options)[:headers]))
|
46
48
|
end
|
47
49
|
|
48
|
-
def post_chunked(path, options={})
|
49
|
-
authenticate(configuration + path)
|
50
|
-
result = ""
|
51
|
-
|
52
|
-
response = @client.post(configuration + path, merge_options(options)[:body], merge_options(options)[:headers]) do |chunk|
|
53
|
-
result << chunk
|
54
|
-
end
|
55
|
-
|
56
|
-
r = evaluate_chunk_response(response, result)
|
57
|
-
|
58
|
-
if r.last["status"] > 399
|
59
|
-
handle_4xx_500_response(r.last["status"], r.last["body"] || r.last )
|
60
|
-
end
|
61
|
-
|
62
|
-
r
|
63
|
-
end
|
64
|
-
|
65
50
|
def put(path, options={})
|
66
51
|
authenticate(configuration + path)
|
67
52
|
evaluate_response(@client.put(configuration + path, merge_options(options)[:body], merge_options(options)[:headers]))
|
@@ -121,19 +106,36 @@ module Neography
|
|
121
106
|
end
|
122
107
|
|
123
108
|
def evaluate_response(response)
|
124
|
-
|
125
|
-
|
126
|
-
|
109
|
+
if response.http_header.request_uri.request_uri == "/db/data/batch"
|
110
|
+
code, body, parsed = handle_batch(response)
|
111
|
+
else
|
112
|
+
code = response.code
|
113
|
+
body = response.body
|
114
|
+
parsed = false
|
115
|
+
end
|
116
|
+
return_result(code, body, parsed)
|
117
|
+
end
|
118
|
+
|
119
|
+
def handle_batch(response)
|
120
|
+
code = 200
|
121
|
+
body = @parser.json(response.body)
|
122
|
+
body.each do |result|
|
123
|
+
if result["status"] >= 400
|
124
|
+
code = result["status"]
|
125
|
+
break
|
126
|
+
end
|
127
|
+
end
|
128
|
+
return code, body, true
|
127
129
|
end
|
128
130
|
|
129
|
-
def return_result(code, body)
|
131
|
+
def return_result(code, body, parsed)
|
130
132
|
case code
|
131
133
|
when 200
|
132
|
-
@logger.debug "OK" if @log_enabled
|
133
|
-
@parser.json(body)
|
134
|
+
@logger.debug "OK, created #{body}" if @log_enabled
|
135
|
+
parsed ? body : @parser.json(body)
|
134
136
|
when 201
|
135
137
|
@logger.debug "OK, created #{body}" if @log_enabled
|
136
|
-
r = @parser.json(body)
|
138
|
+
r = parsed ? body : @parser.json(body)
|
137
139
|
r.extend(WasCreated)
|
138
140
|
r
|
139
141
|
when 204
|
@@ -147,24 +149,32 @@ module Neography
|
|
147
149
|
|
148
150
|
def handle_4xx_500_response(code, body)
|
149
151
|
if body.nil? or body == ""
|
150
|
-
parsed_body = {
|
151
|
-
|
152
|
-
stacktrace = ""
|
152
|
+
parsed_body = {"message" => "No error message returned from server.",
|
153
|
+
"stacktrace" => "No stacktrace returned from server." }
|
153
154
|
elsif body.is_a? Hash
|
154
155
|
parsed_body = body
|
155
|
-
|
156
|
-
|
156
|
+
elsif body.is_a? Array
|
157
|
+
body.each do |result|
|
158
|
+
if result["status"] >= 400
|
159
|
+
parsed_body = result["body"] || result
|
160
|
+
break
|
161
|
+
end
|
162
|
+
end
|
157
163
|
else
|
158
164
|
parsed_body = @parser.json(body)
|
159
|
-
message = parsed_body["message"]
|
160
|
-
stacktrace = parsed_body["stacktrace"]
|
161
165
|
end
|
162
166
|
|
163
|
-
|
167
|
+
message = parsed_body["message"]
|
168
|
+
stacktrace = parsed_body["stacktrace"]
|
164
169
|
|
170
|
+
@logger.error "#{code} error: #{body}" if @log_enabled
|
171
|
+
raise_errors(code, parsed_body["exception"], message, stacktrace)
|
172
|
+
end
|
173
|
+
|
174
|
+
def raise_errors(code, exception, message, stacktrace)
|
165
175
|
case code
|
166
176
|
when 400, 404
|
167
|
-
case
|
177
|
+
case exception
|
168
178
|
when /SyntaxException/ ; raise SyntaxException.new(message, code, stacktrace)
|
169
179
|
when /this is not a query/ ; raise SyntaxException.new(message, code, stacktrace)
|
170
180
|
when /PropertyValueException/ ; raise PropertyValueException.new(message, code, stacktrace)
|
@@ -184,6 +194,7 @@ module Neography
|
|
184
194
|
else
|
185
195
|
raise NeographyError.new(message, code, stacktrace)
|
186
196
|
end
|
197
|
+
|
187
198
|
end
|
188
199
|
|
189
200
|
def parse_string_options(options)
|
data/lib/neography/node.rb
CHANGED
@@ -20,7 +20,7 @@ module Neography
|
|
20
20
|
def load(node, db = Neography::Rest.new)
|
21
21
|
raise ArgumentError.new("syntax deprecated") if node.is_a?(Neography::Rest)
|
22
22
|
|
23
|
-
node = db.get_node(node)
|
23
|
+
node = db.get_node(node) if (node.to_s.match(/^\d+$/) or node.to_s.split("/").last.match(/^\d+$/))
|
24
24
|
if node
|
25
25
|
node = self.new(node)
|
26
26
|
node.neo_server = db
|
@@ -63,15 +63,15 @@ module Neography
|
|
63
63
|
|
64
64
|
if @get.include?("node")
|
65
65
|
path["nodes"].each_with_index do |n, i|
|
66
|
-
@loaded_nodes[n
|
67
|
-
paths[i * 2] = @loaded_nodes[n
|
66
|
+
@loaded_nodes[get_id(n)] = Neography::Node.load(n) if @loaded_nodes.at(get_id(n)).nil?
|
67
|
+
paths[i * 2] = @loaded_nodes[get_id(n)]
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
71
|
if @get.include?("rel")
|
72
72
|
path["relationships"].each_with_index do |r, i|
|
73
|
-
@loaded_rels[r
|
74
|
-
paths[i * 2 + 1] = @loaded_rels[r
|
73
|
+
@loaded_rels[get_id(r)] = Neography::Relationship.load(r) if @loaded_rels.at(get_id(r)).nil?
|
74
|
+
paths[i * 2 + 1] = @loaded_rels[get_id(r)]
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -90,6 +90,11 @@ module Neography
|
|
90
90
|
@from.neo_server.get_paths(@from, @to, @relationships, @depth, @algorithm)
|
91
91
|
end
|
92
92
|
end
|
93
|
+
|
94
|
+
private
|
95
|
+
def get_id(object)
|
96
|
+
object.split('/').last.to_i
|
97
|
+
end
|
93
98
|
|
94
99
|
end
|
95
100
|
end
|
@@ -5,11 +5,23 @@ module Neography
|
|
5
5
|
def initialize(hash=nil)
|
6
6
|
@table = {}
|
7
7
|
if hash
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
if hash["self"] # coming from REST API
|
9
|
+
@neo_id = hash["self"].split('/').last
|
10
|
+
data = hash["data"]
|
11
|
+
elsif hash.is_a? Neography::Node # is already a Neography::Node
|
12
|
+
@neo_id = hash.neo_id
|
13
|
+
data = Hash[*hash.attributes.collect{|x| [x.to_sym, hash.send(x)]}.flatten]
|
14
|
+
elsif hash["data"] # coming from CYPHER
|
15
|
+
@neo_id = hash["data"].first.first["self"].split('/').last
|
16
|
+
data = hash["data"].first.first["data"]
|
12
17
|
end
|
18
|
+
else
|
19
|
+
data = []
|
20
|
+
end
|
21
|
+
|
22
|
+
for k,v in data
|
23
|
+
@table[k.to_sym] = v
|
24
|
+
new_ostruct_member(k)
|
13
25
|
end
|
14
26
|
end
|
15
27
|
|
data/lib/neography/rest.rb
CHANGED
@@ -6,6 +6,7 @@ require 'neography/rest/paths'
|
|
6
6
|
require 'neography/rest/properties'
|
7
7
|
require 'neography/rest/indexes'
|
8
8
|
require 'neography/rest/auto_indexes'
|
9
|
+
require 'neography/rest/schema_indexes'
|
9
10
|
|
10
11
|
require 'neography/rest/nodes'
|
11
12
|
require 'neography/rest/node_properties'
|
@@ -15,15 +16,18 @@ require 'neography/rest/node_indexes'
|
|
15
16
|
require 'neography/rest/node_auto_indexes'
|
16
17
|
require 'neography/rest/node_traversal'
|
17
18
|
require 'neography/rest/node_paths'
|
19
|
+
require 'neography/rest/node_labels'
|
18
20
|
require 'neography/rest/relationships'
|
19
21
|
require 'neography/rest/relationship_properties'
|
20
22
|
require 'neography/rest/relationship_indexes'
|
21
23
|
require 'neography/rest/relationship_auto_indexes'
|
24
|
+
require 'neography/rest/relationship_types'
|
22
25
|
require 'neography/rest/cypher'
|
23
26
|
require 'neography/rest/gremlin'
|
24
27
|
require 'neography/rest/extensions'
|
25
28
|
require 'neography/rest/batch'
|
26
29
|
require 'neography/rest/clean'
|
30
|
+
require 'neography/rest/transactions'
|
27
31
|
|
28
32
|
require 'neography/errors'
|
29
33
|
|
@@ -48,21 +52,101 @@ module Neography
|
|
48
52
|
@other_node_relationships = OtherNodeRelationships.new(@connection)
|
49
53
|
@node_indexes = NodeIndexes.new(@connection)
|
50
54
|
@node_auto_indexes = NodeAutoIndexes.new(@connection)
|
55
|
+
@schema_indexes = SchemaIndexes.new(@connection)
|
51
56
|
@node_traversal = NodeTraversal.new(@connection)
|
52
57
|
@node_paths = NodePaths.new(@connection)
|
58
|
+
@node_labels = NodeLabels.new(@connection)
|
53
59
|
|
54
60
|
@relationships = Relationships.new(@connection)
|
55
61
|
@relationship_properties = RelationshipProperties.new(@connection)
|
56
62
|
@relationship_indexes = RelationshipIndexes.new(@connection)
|
57
63
|
@relationship_auto_indexes = RelationshipAutoIndexes.new(@connection)
|
64
|
+
@relationship_types = RelationshipTypes.new(@connection)
|
58
65
|
|
59
66
|
@cypher = Cypher.new(@connection)
|
60
67
|
@gremlin = Gremlin.new(@connection)
|
61
68
|
@extensions = Extensions.new(@connection)
|
62
69
|
@batch = Batch.new(@connection)
|
63
70
|
@clean = Clean.new(@connection)
|
71
|
+
@transactions = Transactions.new(@connection)
|
64
72
|
end
|
65
73
|
|
74
|
+
# meta-data
|
75
|
+
|
76
|
+
def list_relationship_types
|
77
|
+
@relationship_types.list
|
78
|
+
end
|
79
|
+
|
80
|
+
# labels
|
81
|
+
|
82
|
+
def list_labels
|
83
|
+
@node_labels.list
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_label(id, label)
|
87
|
+
@node_labels.add(id, label)
|
88
|
+
end
|
89
|
+
|
90
|
+
def set_label(id, label)
|
91
|
+
@node_labels.set(id, label)
|
92
|
+
end
|
93
|
+
|
94
|
+
def delete_label(id, label)
|
95
|
+
@node_labels.delete(id, label)
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_node_labels(id)
|
99
|
+
@node_labels.get(id)
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_nodes_labeled(label)
|
103
|
+
@node_labels.get_nodes(label)
|
104
|
+
end
|
105
|
+
|
106
|
+
def find_nodes_labeled(label, hash)
|
107
|
+
@node_labels.find_nodes(label, hash)
|
108
|
+
end
|
109
|
+
|
110
|
+
# schema indexes
|
111
|
+
|
112
|
+
def get_schema_index(label)
|
113
|
+
@schema_indexes.list(label)
|
114
|
+
end
|
115
|
+
|
116
|
+
def create_schema_index(label, properties)
|
117
|
+
@schema_indexes.create(label, properties)
|
118
|
+
end
|
119
|
+
|
120
|
+
def delete_schema_index(label, property)
|
121
|
+
@schema_indexes.drop(label, property)
|
122
|
+
end
|
123
|
+
|
124
|
+
# transactions
|
125
|
+
|
126
|
+
def begin_transaction(statements=nil)
|
127
|
+
@transactions.begin(statements)
|
128
|
+
end
|
129
|
+
|
130
|
+
def in_transaction(tx, statements)
|
131
|
+
@transactions.add(tx, statements)
|
132
|
+
end
|
133
|
+
|
134
|
+
def keep_transaction(tx)
|
135
|
+
@transactions.add(tx)
|
136
|
+
end
|
137
|
+
|
138
|
+
def commit_transaction(tx, statements=[])
|
139
|
+
if tx.is_a?(Array)
|
140
|
+
@transactions.begin(tx, "commit")
|
141
|
+
else
|
142
|
+
@transactions.commit(tx, statements)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def rollback_transaction(tx)
|
147
|
+
@transactions.add(tx)
|
148
|
+
end
|
149
|
+
|
66
150
|
# nodes
|
67
151
|
|
68
152
|
def get_root
|
@@ -331,8 +415,8 @@ module Neography
|
|
331
415
|
|
332
416
|
# cypher query
|
333
417
|
|
334
|
-
def execute_query(query, params = {})
|
335
|
-
@cypher.query(query, params)
|
418
|
+
def execute_query(query, params = {}, cypher_options = nil)
|
419
|
+
@cypher.query(query, params, cypher_options)
|
336
420
|
end
|
337
421
|
|
338
422
|
# gremlin script
|
data/lib/neography/rest/batch.rb
CHANGED
@@ -11,29 +11,21 @@ module Neography
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def execute(*args)
|
14
|
-
batch(
|
15
|
-
end
|
16
|
-
|
17
|
-
def not_streaming(*args)
|
18
|
-
batch({}, *args)
|
14
|
+
batch(*args)
|
19
15
|
end
|
20
16
|
|
21
17
|
private
|
22
18
|
|
23
|
-
def batch(
|
19
|
+
def batch(*args)
|
24
20
|
batch = []
|
25
21
|
Array(args).each_with_index do |c, i|
|
26
22
|
batch << {:id => i }.merge(get_batch(c))
|
27
23
|
end
|
28
24
|
options = {
|
29
25
|
:body => batch.to_json,
|
30
|
-
:headers => json_content_type
|
26
|
+
:headers => json_content_type
|
31
27
|
}
|
32
|
-
|
33
|
-
@connection.post(batch_path, options)
|
34
|
-
else
|
35
|
-
@connection.post_chunked(batch_path, options)
|
36
|
-
end
|
28
|
+
@connection.post(batch_path, options)
|
37
29
|
end
|
38
30
|
|
39
31
|
def get_batch(args)
|
@@ -7,7 +7,7 @@ module Neography
|
|
7
7
|
@connection = connection
|
8
8
|
end
|
9
9
|
|
10
|
-
def query(query, parameters = {})
|
10
|
+
def query(query, parameters = {}, cypher_options = nil)
|
11
11
|
options = {
|
12
12
|
:body => {
|
13
13
|
:query => query,
|
@@ -15,8 +15,17 @@ module Neography
|
|
15
15
|
}.to_json,
|
16
16
|
:headers => json_content_type.merge({'Accept' => 'application/json;stream=true'})
|
17
17
|
}
|
18
|
-
|
19
|
-
@connection.post(
|
18
|
+
|
19
|
+
@connection.post(optioned_path(cypher_options), options)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def optioned_path(cypher_options = nil)
|
24
|
+
return @connection.cypher_path unless cypher_options
|
25
|
+
options = []
|
26
|
+
options << "includeStats=true" if cypher_options[:stats]
|
27
|
+
options << "profile=true" if cypher_options[:profile]
|
28
|
+
@connection.cypher_path + "?" + options.join("&")
|
20
29
|
end
|
21
30
|
|
22
31
|
end
|
@@ -21,6 +21,18 @@ module Neography
|
|
21
21
|
{'Content-Type' => 'application/json'}
|
22
22
|
end
|
23
23
|
|
24
|
+
def parse_direction(direction)
|
25
|
+
case direction
|
26
|
+
when :incoming, "incoming", :in, "in"
|
27
|
+
"in"
|
28
|
+
when :outgoing, "outgoing", :out, "out"
|
29
|
+
"out"
|
30
|
+
else
|
31
|
+
"all"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
24
36
|
end
|
25
37
|
end
|
26
38
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Neography
|
2
|
+
class Rest
|
3
|
+
class NodeLabels
|
4
|
+
extend Neography::Rest::Paths
|
5
|
+
include Neography::Rest::Helpers
|
6
|
+
|
7
|
+
add_path :base, "/labels"
|
8
|
+
add_path :node, "/node/:id/labels"
|
9
|
+
add_path :nodes, "/label/:label/nodes"
|
10
|
+
add_path :find, "/label/:label/nodes?:property=%22:value%22"
|
11
|
+
add_path :delete, "/node/:id/labels/:label"
|
12
|
+
|
13
|
+
def initialize(connection)
|
14
|
+
@connection = connection
|
15
|
+
end
|
16
|
+
|
17
|
+
def list
|
18
|
+
@connection.get(base_path)
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(id)
|
22
|
+
@connection.get(node_path(:id => id))
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_nodes(label)
|
26
|
+
@connection.get(nodes_path(:label => label))
|
27
|
+
end
|
28
|
+
|
29
|
+
def find_nodes(label, hash)
|
30
|
+
@connection.get(find_path(:label => label, :property => hash.keys.first, :value => hash.values.first))
|
31
|
+
end
|
32
|
+
|
33
|
+
def add(id, label)
|
34
|
+
options = {
|
35
|
+
:body => (
|
36
|
+
label
|
37
|
+
).to_json,
|
38
|
+
:headers => json_content_type
|
39
|
+
}
|
40
|
+
@connection.post(node_path(:id => id), options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def set(id, label)
|
44
|
+
options = {
|
45
|
+
:body => (
|
46
|
+
Array(label)
|
47
|
+
).to_json,
|
48
|
+
:headers => json_content_type
|
49
|
+
}
|
50
|
+
@connection.put(node_path(:id => id), options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(id, label)
|
54
|
+
@connection.delete(delete_path(:id => id, :label => label))
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|