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.
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