neography 1.0.10 → 1.0.11
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.
- 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
|