neography 1.0.10 → 1.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.travis.yml +1 -1
  2. data/README.md +1 -0
  3. data/lib/neography/connection.rb +45 -34
  4. data/lib/neography/node.rb +1 -1
  5. data/lib/neography/path_traverser.rb +9 -4
  6. data/lib/neography/property_container.rb +16 -4
  7. data/lib/neography/rest.rb +86 -2
  8. data/lib/neography/rest/batch.rb +4 -12
  9. data/lib/neography/rest/cypher.rb +12 -3
  10. data/lib/neography/rest/helpers.rb +12 -0
  11. data/lib/neography/rest/node_labels.rb +60 -0
  12. data/lib/neography/rest/node_relationships.rb +0 -11
  13. data/lib/neography/rest/other_node_relationships.rb +1 -12
  14. data/lib/neography/rest/relationship_types.rb +18 -0
  15. data/lib/neography/rest/schema_indexes.rb +34 -0
  16. data/lib/neography/rest/transactions.rb +83 -0
  17. data/lib/neography/tasks.rb +1 -1
  18. data/lib/neography/version.rb +1 -1
  19. data/spec/integration/node_spec.rb +13 -0
  20. data/spec/integration/rest_batch_spec.rb +11 -15
  21. data/spec/integration/rest_batch_streaming_spec.rb +2 -2
  22. data/spec/integration/rest_gremlin_fail_spec.rb +4 -4
  23. data/spec/integration/rest_header_spec.rb +1 -1
  24. data/spec/integration/rest_labels_spec.rb +131 -0
  25. data/spec/integration/rest_plugin_spec.rb +32 -3
  26. data/spec/integration/rest_relationship_types_spec.rb +18 -0
  27. data/spec/integration/rest_schema_index_spec.rb +32 -0
  28. data/spec/integration/rest_transaction_spec.rb +100 -0
  29. data/spec/spec_helper.rb +8 -1
  30. data/spec/unit/connection_spec.rb +1 -1
  31. data/spec/unit/node_spec.rb +1 -1
  32. data/spec/unit/rest/batch_spec.rb +21 -21
  33. data/spec/unit/rest/labels_spec.rb +73 -0
  34. data/spec/unit/rest/relationship_types_spec.rb +16 -0
  35. data/spec/unit/rest/schema_index_spec.rb +31 -0
  36. data/spec/unit/rest/transactions_spec.rb +44 -0
  37. metadata +22 -2
@@ -1,4 +1,4 @@
1
- script: "bundle exec rake neo4j:install['enterprise','1.8.2'] neo4j:start spec --trace"
1
+ script: "bundle exec rake neo4j:install['enterprise','2.0.0-M03'] neo4j:start spec --trace"
2
2
  language: ruby
3
3
  rvm:
4
4
  - 1.9.3
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.
@@ -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)#.merge!(@parser)
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
- code = response.code
125
- body = response.body
126
- return_result(code, body)
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) #response.parsed_response
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) #response.parsed_response
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
- message = "No error message returned from server."
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
- message = parsed_body["message"]
156
- stacktrace = parsed_body["stacktrace"]
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
- @logger.error "#{code} error: #{body}" if @log_enabled
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 parsed_body["exception"]
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)
@@ -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.split('/').last.to_i] = Neography::Node.load(n) if @loaded_nodes.at(n.split('/').last.to_i).nil?
67
- paths[i * 2] = @loaded_nodes[n.split('/').last.to_i]
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.split('/').last.to_i] = Neography::Relationship.load(r) if @loaded_rels.at(r.split('/').last.to_i).nil?
74
- paths[i * 2 + 1] = @loaded_rels[r.split('/').last.to_i]
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
- @neo_id = hash["self"].split('/').last
9
- for k,v in hash["data"]
10
- @table[k.to_sym] = v
11
- new_ostruct_member(k)
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
 
@@ -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
@@ -11,29 +11,21 @@ module Neography
11
11
  end
12
12
 
13
13
  def execute(*args)
14
- batch({'Accept' => 'application/json;stream=true'}, *args)
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(accept_header, *args)
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.merge(accept_header)
26
+ :headers => json_content_type
31
27
  }
32
- if accept_header.empty?
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(@connection.cypher_path, options)
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