vagas-orientdb4r 0.5.2
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 +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +26 -0
- data/.travis.yml +18 -0
- data/Gemfile +9 -0
- data/LICENSE +202 -0
- data/README.rdoc +124 -0
- data/Rakefile +43 -0
- data/changelog.txt +60 -0
- data/ci/initialize-ci.sh +36 -0
- data/fstudy/design_v1.dia +0 -0
- data/fstudy/design_v1.png +0 -0
- data/fstudy/domain_model.dia +0 -0
- data/fstudy/domain_model.png +0 -0
- data/fstudy/flat_class_perf.rb +56 -0
- data/fstudy/sample1_object_diagram.dia +0 -0
- data/fstudy/sample1_object_diagram.png +0 -0
- data/fstudy/study_case.rb +87 -0
- data/fstudy/technical_feasibility.rb +256 -0
- data/lib/orientdb4r.rb +115 -0
- data/lib/orientdb4r/bin/client.rb +86 -0
- data/lib/orientdb4r/bin/connection.rb +29 -0
- data/lib/orientdb4r/bin/constants.rb +20 -0
- data/lib/orientdb4r/bin/io.rb +38 -0
- data/lib/orientdb4r/bin/protocol28.rb +101 -0
- data/lib/orientdb4r/bin/protocol_factory.rb +25 -0
- data/lib/orientdb4r/chained_error.rb +37 -0
- data/lib/orientdb4r/client.rb +364 -0
- data/lib/orientdb4r/load_balancing.rb +113 -0
- data/lib/orientdb4r/node.rb +42 -0
- data/lib/orientdb4r/rest/client.rb +517 -0
- data/lib/orientdb4r/rest/excon_node.rb +115 -0
- data/lib/orientdb4r/rest/model.rb +159 -0
- data/lib/orientdb4r/rest/node.rb +43 -0
- data/lib/orientdb4r/rest/restclient_node.rb +77 -0
- data/lib/orientdb4r/rid.rb +54 -0
- data/lib/orientdb4r/utils.rb +203 -0
- data/lib/orientdb4r/version.rb +39 -0
- data/orientdb4r.gemspec +37 -0
- data/test/bin/test_client.rb +21 -0
- data/test/readme_sample.rb +38 -0
- data/test/test_client.rb +93 -0
- data/test/test_database.rb +261 -0
- data/test/test_ddo.rb +237 -0
- data/test/test_dmo.rb +115 -0
- data/test/test_document_crud.rb +184 -0
- data/test/test_gremlin.rb +52 -0
- data/test/test_helper.rb +10 -0
- data/test/test_loadbalancing.rb +81 -0
- data/test/test_utils.rb +67 -0
- metadata +136 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Orientdb4r
|
|
2
|
+
|
|
3
|
+
###
|
|
4
|
+
# This class represents a single sever/node
|
|
5
|
+
# in the Distributed Multi-Master Architecture.
|
|
6
|
+
class Node
|
|
7
|
+
include Utils
|
|
8
|
+
|
|
9
|
+
attr_reader :host, :port # they are immutable
|
|
10
|
+
attr_reader :session_id
|
|
11
|
+
|
|
12
|
+
###
|
|
13
|
+
# Constructor.
|
|
14
|
+
def initialize(host, port)
|
|
15
|
+
raise ArgumentError, 'host cannot be blank' if blank? host
|
|
16
|
+
raise ArgumentError, 'port cannot be blank' if blank? port
|
|
17
|
+
@host = host
|
|
18
|
+
@port = port
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
###
|
|
23
|
+
# Cleans up resources used by the node.
|
|
24
|
+
def cleanup
|
|
25
|
+
@session_id = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
###
|
|
30
|
+
# Gets URL of the remote node.
|
|
31
|
+
def url
|
|
32
|
+
raise NotImplementedError, 'this should be overridden by subclass'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def to_s #:nodoc:
|
|
37
|
+
"Node(host=#{host},port=#{port})"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
module Orientdb4r
|
|
2
|
+
|
|
3
|
+
class RestClient < Client
|
|
4
|
+
include Aop2
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
before [:query, :command, :gremlin, :batch], :assert_connected
|
|
8
|
+
before [:create_class, :get_class, :class_exists?, :drop_class, :create_property], :assert_connected
|
|
9
|
+
before [:create_document, :get_document, :update_document, :delete_document], :assert_connected
|
|
10
|
+
around [:query, :command], :time_around
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def initialize(options) #:nodoc:
|
|
14
|
+
super()
|
|
15
|
+
options_pattern = {
|
|
16
|
+
:host => 'localhost', :port => 2480, :ssl => false,
|
|
17
|
+
:nodes => :optional,
|
|
18
|
+
:connection_library => :restclient,
|
|
19
|
+
# :connection_library => :excon,
|
|
20
|
+
:load_balancing => :sequence,
|
|
21
|
+
:proxy => :optional,
|
|
22
|
+
:user_agent => :optional,
|
|
23
|
+
:lb_recover_time => :optional
|
|
24
|
+
}
|
|
25
|
+
verify_and_sanitize_options(options, options_pattern)
|
|
26
|
+
|
|
27
|
+
# fake nodes for single server
|
|
28
|
+
if options[:nodes].nil?
|
|
29
|
+
options[:nodes] = [{:host => options[:host], :port => options[:port], :ssl => options[:ssl]}]
|
|
30
|
+
end
|
|
31
|
+
raise ArgumentError, 'nodes has to be array' unless options[:nodes].is_a? Array
|
|
32
|
+
|
|
33
|
+
# instantiate nodes according to HTTP library
|
|
34
|
+
@connection_library = options[:connection_library]
|
|
35
|
+
node_clazz = case connection_library
|
|
36
|
+
when :restclient then Orientdb4r::RestClientNode
|
|
37
|
+
when :excon then Orientdb4r::ExconNode
|
|
38
|
+
else raise ArgumentError, "unknown connection library: #{connection_library}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# nodes
|
|
42
|
+
options[:nodes].each do |node_options|
|
|
43
|
+
verify_and_sanitize_options(node_options, options_pattern)
|
|
44
|
+
@nodes << node_clazz.new(node_options[:host], node_options[:port], node_options[:ssl])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# load balancing
|
|
48
|
+
@load_balancing = options[:load_balancing]
|
|
49
|
+
@lb_strategy = case load_balancing
|
|
50
|
+
when :sequence then Orientdb4r::Sequence.new nodes.size
|
|
51
|
+
when :round_robin then Orientdb4r::RoundRobin.new nodes.size
|
|
52
|
+
else raise ArgumentError, "unknow load balancing type: #{load_balancing}"
|
|
53
|
+
end
|
|
54
|
+
# recover time
|
|
55
|
+
recover_time = options[:lb_recover_time]
|
|
56
|
+
@lb_strategy.recover_time = recover_time.to_i unless recover_time.nil?
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# proxy
|
|
60
|
+
@proxy = options[:proxy]
|
|
61
|
+
unless proxy.nil?
|
|
62
|
+
case connection_library
|
|
63
|
+
when :restclient then ::RestClient.proxy = proxy
|
|
64
|
+
when :excon then nodes.each { |node| node.proxy = proxy }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# user-agent
|
|
69
|
+
agent = options[:user_agent]
|
|
70
|
+
unless agent.nil?
|
|
71
|
+
nodes.each { |node| node.user_agent = agent }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Orientdb4r::logger.info "client initialized with #{@nodes.size} node(s) "
|
|
75
|
+
Orientdb4r::logger.info "connection_library=#{options[:connection_library]}, load_balancing=#{load_balancing}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# --------------------------------------------------------------- CONNECTION
|
|
80
|
+
|
|
81
|
+
def connect(options) #:nodoc:
|
|
82
|
+
options_pattern = { :database => :mandatory, :user => :mandatory, :password => :mandatory }
|
|
83
|
+
verify_and_sanitize_options(options, options_pattern)
|
|
84
|
+
|
|
85
|
+
@database = options[:database]
|
|
86
|
+
@user = options[:user]
|
|
87
|
+
@password = options[:password]
|
|
88
|
+
|
|
89
|
+
@nodes.each { |node| node.cleanup } # destroy all used session <= problem in 1.3.0-SNAPSHOT
|
|
90
|
+
begin
|
|
91
|
+
http_response = call_server(:method => :get, :uri => "connect/#{@database}")
|
|
92
|
+
rescue
|
|
93
|
+
@connected = false
|
|
94
|
+
@user = nil
|
|
95
|
+
@password = nil
|
|
96
|
+
@database = nil
|
|
97
|
+
@nodes.each { |node| node.cleanup }
|
|
98
|
+
raise ConnectionError
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
rslt = process_response http_response
|
|
102
|
+
# no metadata in connect v1.4.0+
|
|
103
|
+
# https://groups.google.com/forum/?fromgroups=#!topic/orient-database/R0VoOfIyDng
|
|
104
|
+
#decorate_classes_with_model(rslt['classes']) unless rslt['classes'].nil?
|
|
105
|
+
|
|
106
|
+
if @connection_library == :excon
|
|
107
|
+
Orientdb4r::logger.info "successfully connected to server, code=#{http_response.code}"
|
|
108
|
+
else
|
|
109
|
+
Orientdb4r::logger.info "successfully connected to server, code=#{rslt.code}"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
@connected = true
|
|
113
|
+
@connected
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def disconnect #:nodoc:
|
|
118
|
+
return unless @connected
|
|
119
|
+
|
|
120
|
+
begin
|
|
121
|
+
call_server(:method => :get, :uri => 'disconnect')
|
|
122
|
+
# https://groups.google.com/forum/?fromgroups#!topic/orient-database/5MAMCvFavTc
|
|
123
|
+
# Disconnect doesn't require you're authenticated.
|
|
124
|
+
# It always returns 401 because some browsers intercept this and avoid to reuse the same session again.
|
|
125
|
+
ensure
|
|
126
|
+
@connected = false
|
|
127
|
+
@user = nil
|
|
128
|
+
@password = nil
|
|
129
|
+
@database = nil
|
|
130
|
+
@nodes.each { |node| node.cleanup }
|
|
131
|
+
Orientdb4r::logger.debug 'disconnected from server'
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def server(options={}) #:nodoc:
|
|
137
|
+
options_pattern = { :user => :optional, :password => :optional }
|
|
138
|
+
verify_options(options, options_pattern)
|
|
139
|
+
|
|
140
|
+
params = { :method => :get, :uri => 'server' }
|
|
141
|
+
params[:no_session] = true # out of existing session which represents an already done authentication
|
|
142
|
+
|
|
143
|
+
# additional authentication allowed, overriden in 'call_server' if not defined
|
|
144
|
+
params[:user] = options[:user] if options.include? :user
|
|
145
|
+
params[:password] = options[:password] if options.include? :password
|
|
146
|
+
|
|
147
|
+
response = call_server params
|
|
148
|
+
process_response(response)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ----------------------------------------------------------------- DATABASE
|
|
153
|
+
|
|
154
|
+
def create_database(options) #:nodoc:
|
|
155
|
+
options_pattern = {
|
|
156
|
+
:database => :mandatory, :storage => :memory, :type => :document,
|
|
157
|
+
:user => :optional, :password => :optional
|
|
158
|
+
}
|
|
159
|
+
verify_and_sanitize_options(options, options_pattern)
|
|
160
|
+
verify_options(options.select {|k,v| k === :storage or k === :type}, {:storage => [:memory, :plocal], :type => [:document, :graph]})
|
|
161
|
+
|
|
162
|
+
params = { :method => :post, :uri => "database/#{options[:database]}/#{options[:storage].to_s}/#{options[:type].to_s}" }
|
|
163
|
+
params[:no_session] = true # out of existing session which represents an already done authentication
|
|
164
|
+
|
|
165
|
+
# additional authentication allowed, overriden in 'call_server' if not defined
|
|
166
|
+
params[:user] = options[:user] if options.include? :user
|
|
167
|
+
params[:password] = options[:password] if options.include? :password
|
|
168
|
+
|
|
169
|
+
response = call_server params
|
|
170
|
+
process_response(response)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
#> curl --user admin:admin http://localhost:2480/database/temp
|
|
175
|
+
def get_database(options=nil) #:nodoc:
|
|
176
|
+
raise ArgumentError, 'options have to be a Hash' if !options.nil? and !options.kind_of? Hash
|
|
177
|
+
|
|
178
|
+
if options.nil?
|
|
179
|
+
# use database from connect
|
|
180
|
+
raise ConnectionError, 'client has to be connected if no params' unless connected?
|
|
181
|
+
options = { :database => database }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
options_pattern = { :database => :mandatory, :user => :optional, :password => :optional }
|
|
185
|
+
verify_options(options, options_pattern)
|
|
186
|
+
|
|
187
|
+
params = {:method => :get, :uri => "database/#{options[:database]}"}
|
|
188
|
+
params[:no_session] = true # out of existing session which represents an already done authentication
|
|
189
|
+
|
|
190
|
+
# additional authentication allowed, overriden in 'call_server' if not defined
|
|
191
|
+
params[:user] = options[:user] if options.include? :user
|
|
192
|
+
params[:password] = options[:password] if options.include? :password
|
|
193
|
+
|
|
194
|
+
response = call_server params
|
|
195
|
+
|
|
196
|
+
# NotFoundError cannot be raised - no way how to recognize from 401 bad auth
|
|
197
|
+
rslt = process_response response
|
|
198
|
+
decorate_classes_with_model(rslt['classes']) unless rslt['classes'].nil?
|
|
199
|
+
rslt
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def delete_database(options) #:nodoc:
|
|
204
|
+
options_pattern = {
|
|
205
|
+
:database => :mandatory, :user => :optional, :password => :optional
|
|
206
|
+
}
|
|
207
|
+
verify_and_sanitize_options(options, options_pattern)
|
|
208
|
+
|
|
209
|
+
params = { :method => :delete, :uri => "database/#{options[:database]}" }
|
|
210
|
+
params[:no_session] = true # out of existing session which represents an already done authentication
|
|
211
|
+
|
|
212
|
+
# additional authentication allowed, overriden in 'call_server' if not defined
|
|
213
|
+
params[:user] = options[:user] if options.include? :user
|
|
214
|
+
params[:password] = options[:password] if options.include? :password
|
|
215
|
+
|
|
216
|
+
response = call_server params
|
|
217
|
+
process_response(response)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
#> curl --user root:root http://localhost:2480/listDatabases
|
|
222
|
+
def list_databases(options=nil) #:nodoc:
|
|
223
|
+
verify_and_sanitize_options(options, { :user => :optional, :password => :optional })
|
|
224
|
+
|
|
225
|
+
params = { :method => :get, :uri => 'listDatabases', :no_session => true }
|
|
226
|
+
# additional authentication allowed, overriden in 'call_server' if not defined
|
|
227
|
+
params[:user] = options[:user] if options.include? :user
|
|
228
|
+
params[:password] = options[:password] if options.include? :password
|
|
229
|
+
|
|
230
|
+
response = call_server params
|
|
231
|
+
rslt = process_response(response)
|
|
232
|
+
rslt['databases']
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def export(options=nil) #:nodoc:
|
|
237
|
+
raise ArgumentError, 'options have to be a Hash' if !options.nil? and !options.kind_of? Hash
|
|
238
|
+
|
|
239
|
+
if options.nil? or (!options.nil? and options[:database].nil?)
|
|
240
|
+
# use database from connect
|
|
241
|
+
raise ConnectionError, 'client has to be connected if no database given' unless connected?
|
|
242
|
+
options = {} if options.nil?
|
|
243
|
+
options[:database] = database
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
options_pattern = { :database => :mandatory, :user => :optional, :password => :optional, :file => :optional }
|
|
247
|
+
verify_options(options, options_pattern)
|
|
248
|
+
|
|
249
|
+
params = {:method => :get, :uri => "export/#{options[:database]}"}
|
|
250
|
+
params[:no_session] = true # out of existing session which represents an already done authentication
|
|
251
|
+
|
|
252
|
+
# additional authentication allowed, overriden in 'call_server' if not defined
|
|
253
|
+
params[:user] = options[:user] if options.include? :user
|
|
254
|
+
params[:password] = options[:password] if options.include? :password
|
|
255
|
+
|
|
256
|
+
response = call_server params
|
|
257
|
+
rslt = process_response(response)
|
|
258
|
+
|
|
259
|
+
filename = options[:file]
|
|
260
|
+
filename = response.headers[:content_disposition].split('filename=')[-1] if filename.nil?
|
|
261
|
+
File.open(filename, 'w') do |f|
|
|
262
|
+
f.write rslt
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
filename
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def import(options=nil) #:nodoc:
|
|
270
|
+
raise NotImplementedError, 'not working via REST API, see here for more info: https://github.com/nuvolabase/orientdb/issues/1345'
|
|
271
|
+
# params = {:method => :post, :uri => 'import/'}
|
|
272
|
+
# response = call_server params
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ---------------------------------------------------------------------- SQL
|
|
277
|
+
|
|
278
|
+
def query(sql, options=nil) #:nodoc:
|
|
279
|
+
raise ArgumentError, 'query is blank' if blank? sql
|
|
280
|
+
|
|
281
|
+
options_pattern = { :limit => :optional }
|
|
282
|
+
verify_options(options, options_pattern) unless options.nil?
|
|
283
|
+
|
|
284
|
+
limit = ''
|
|
285
|
+
limit = "/#{options[:limit]}" if !options.nil? and options.include?(:limit)
|
|
286
|
+
|
|
287
|
+
response = call_server(:method => :get, :uri => "query/#{@database}/sql/#{CGI::escape(sql)}#{limit}")
|
|
288
|
+
entries = process_response(response) do
|
|
289
|
+
raise NotFoundError, 'record not found' if response.body =~ /ORecordNotFoundException/
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
rslt = entries['result']
|
|
293
|
+
# mixin all document entries (they have '@class' attribute)
|
|
294
|
+
rslt.each { |doc| doc.extend Orientdb4r::DocumentMetadata unless doc['@class'].nil? }
|
|
295
|
+
rslt
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
###
|
|
299
|
+
# Executes a Gremlin command against the database.
|
|
300
|
+
def gremlin(gremlin)
|
|
301
|
+
raise ArgumentError, 'gremlin query is blank' if blank? gremlin
|
|
302
|
+
response = call_server(:method => :post, :uri => "command/#{@database}/gremlin/#{CGI::escape(gremlin)}")
|
|
303
|
+
entries = process_response(response) do
|
|
304
|
+
raise NotFoundError, 'record not found' if response.body =~ /ORecordNotFoundException/
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
rslt = entries['result']
|
|
308
|
+
# mixin all document entries (they have '@class' attribute)
|
|
309
|
+
rslt.each { |doc| doc.extend Orientdb4r::DocumentMetadata unless doc['@class'].nil? }
|
|
310
|
+
rslt
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def command(sql) #:nodoc:
|
|
314
|
+
raise ArgumentError, 'command is blank' if blank? sql
|
|
315
|
+
response = call_server(:method => :post, :uri => "command/#{@database}/sql/#{CGI::escape(sql)}")
|
|
316
|
+
process_response(response)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
###
|
|
320
|
+
# Executes a batch of operations in a single call.
|
|
321
|
+
def batch(operations)
|
|
322
|
+
response = call_server(:method => :post, :uri => "batch/#{@database}", \
|
|
323
|
+
:content_type => 'application/json', :data => operations.to_json)
|
|
324
|
+
process_response(response)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# -------------------------------------------------------------------- CLASS
|
|
329
|
+
|
|
330
|
+
def get_class(name) #:nodoc:
|
|
331
|
+
raise ArgumentError, "class name is blank" if blank?(name)
|
|
332
|
+
|
|
333
|
+
response = call_server(:method => :get, :uri => "class/#{@database}/#{name}")
|
|
334
|
+
rslt = process_response(response)
|
|
335
|
+
|
|
336
|
+
classes = [rslt]
|
|
337
|
+
decorate_classes_with_model(classes)
|
|
338
|
+
clazz = classes[0]
|
|
339
|
+
clazz.extend Orientdb4r::HashExtension
|
|
340
|
+
clazz.extend Orientdb4r::OClass
|
|
341
|
+
unless clazz['properties'].nil? # there can be a class without properties
|
|
342
|
+
clazz.properties.each do |prop|
|
|
343
|
+
prop.extend Orientdb4r::HashExtension
|
|
344
|
+
prop.extend Orientdb4r::Property
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
clazz
|
|
349
|
+
rescue NotFoundError
|
|
350
|
+
raise NotFoundError, 'class not found'
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
# ----------------------------------------------------------------- DOCUMENT
|
|
355
|
+
|
|
356
|
+
def create_document(doc) #:nodoc:
|
|
357
|
+
http_response = call_server(:method => :post, :uri => "document/#{@database}", \
|
|
358
|
+
:content_type => 'application/json', :data => doc.to_json)
|
|
359
|
+
resp = process_response(http_response) do
|
|
360
|
+
raise DataError, 'validation problem' if http_response.body =~ /OValidationException/
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
resp = ::JSON.parse(http_response.body) # workaround: https://groups.google.com/forum/?fromgroups=#!topic/orient-database/UJGAXYpHDmo
|
|
364
|
+
resp.extend Orientdb4r::DocumentMetadata
|
|
365
|
+
resp
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def get_document(rid) #:nodoc:
|
|
370
|
+
rid = Rid.new(rid) unless rid.is_a? Rid
|
|
371
|
+
response = call_server(:method => :get, :uri => "document/#{@database}/#{rid.unprefixed}")
|
|
372
|
+
rslt = process_response(response) do
|
|
373
|
+
raise NotFoundError, 'record not found' if response.body =~ /ORecordNotFoundException/
|
|
374
|
+
raise NotFoundError, 'record not found' if response.body =~ /Record with id .* was not found/ # why after delete?
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
rslt.extend Orientdb4r::DocumentMetadata
|
|
378
|
+
rslt
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def update_document(doc) #:nodoc:
|
|
383
|
+
raise ArgumentError, 'document is nil' if doc.nil?
|
|
384
|
+
raise ArgumentError, 'document has no RID' if doc.doc_rid.nil?
|
|
385
|
+
raise ArgumentError, 'document has no version' if doc.doc_version.nil?
|
|
386
|
+
|
|
387
|
+
rid = doc.doc_rid
|
|
388
|
+
doc.delete '@rid' # will be not updated
|
|
389
|
+
|
|
390
|
+
response = call_server(:method => :put, :uri => "document/#{@database}/#{rid.unprefixed}", \
|
|
391
|
+
:content_type => 'application/json', :data => doc.to_json)
|
|
392
|
+
process_response(response) do
|
|
393
|
+
raise DataError, 'concurrent modification' if response.body =~ /OConcurrentModificationException/
|
|
394
|
+
raise DataError, 'validation problem' if response.body =~ /OValidationException/
|
|
395
|
+
end
|
|
396
|
+
# empty http response
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def delete_document(rid) #:nodoc:
|
|
401
|
+
rid = Rid.new(rid) unless rid.is_a? Rid
|
|
402
|
+
|
|
403
|
+
response = call_server(:method => :delete, :uri => "document/#{@database}/#{rid.unprefixed}")
|
|
404
|
+
process_response(response) do
|
|
405
|
+
raise NotFoundError, 'record not found' if response.body =~ /ORecordNotFoundException/
|
|
406
|
+
end
|
|
407
|
+
# empty http response
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
# ------------------------------------------------------------------ Helpers
|
|
412
|
+
|
|
413
|
+
private
|
|
414
|
+
|
|
415
|
+
####
|
|
416
|
+
# Processes a HTTP response.
|
|
417
|
+
def process_response(response)
|
|
418
|
+
raise ArgumentError, 'response is null' if response.nil?
|
|
419
|
+
|
|
420
|
+
if block_given?
|
|
421
|
+
yield
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
# return code
|
|
426
|
+
if 400 == response.code
|
|
427
|
+
raise InvalidRequestError, compose_error_message(response)
|
|
428
|
+
elsif 401 == response.code
|
|
429
|
+
raise UnauthorizedError, compose_error_message(response)
|
|
430
|
+
elsif 404 == response.code
|
|
431
|
+
raise NotFoundError, compose_error_message(response)
|
|
432
|
+
elsif 409 == response.code
|
|
433
|
+
raise StateConflictError, compose_error_message(response)
|
|
434
|
+
elsif 500 == response.code
|
|
435
|
+
raise ServerError, compose_error_message(response)
|
|
436
|
+
elsif 2 != (response.code / 100)
|
|
437
|
+
raise OrientdbError, "unexpected return code, code=#{response.code}, body=#{compose_error_message(response)}"
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
content_type = response.headers[:content_type] if connection_library == :restclient
|
|
441
|
+
content_type = response.headers['Content-Type'] if connection_library == :excon
|
|
442
|
+
content_type ||= 'text/plain'
|
|
443
|
+
|
|
444
|
+
rslt = case
|
|
445
|
+
when content_type.start_with?('text/plain')
|
|
446
|
+
response.body
|
|
447
|
+
when content_type.start_with?('application/x-gzip')
|
|
448
|
+
response.body
|
|
449
|
+
when content_type.start_with?('application/json')
|
|
450
|
+
::JSON.parse(response.body)
|
|
451
|
+
else
|
|
452
|
+
raise OrientdbError, "unsuported content type: #{content_type}"
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
rslt
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
###
|
|
460
|
+
# Composes message of an error raised if the HTTP response doesn't
|
|
461
|
+
# correspond with expectation.
|
|
462
|
+
def compose_error_message(http_response, max_len=200)
|
|
463
|
+
msg = http_response.body.gsub("\n", ' ')
|
|
464
|
+
if (matcher = msg.match(/"content": "([^"]+)"/))
|
|
465
|
+
msg = matcher[1]
|
|
466
|
+
end
|
|
467
|
+
msg = "#{msg[0..max_len]} ..." if msg.size > max_len
|
|
468
|
+
msg
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
# @deprecated
|
|
473
|
+
def process_restclient_response(response, options={})
|
|
474
|
+
raise ArgumentError, 'response is null' if response.nil?
|
|
475
|
+
|
|
476
|
+
# raise problem if other code than 200
|
|
477
|
+
if options[:mode] == :strict and 200 != response.code
|
|
478
|
+
raise OrientdbError, "unexpeted return code, code=#{response.code}"
|
|
479
|
+
end
|
|
480
|
+
# log warning if other than 200 and raise problem if other code than 'Successful 2xx'
|
|
481
|
+
if options[:mode] == :warning
|
|
482
|
+
if 200 != response.code and 2 == (response.code / 100)
|
|
483
|
+
Orientdb4r::logger.warn "expected return code 200, but received #{response.code}"
|
|
484
|
+
elseif 200 != response.code
|
|
485
|
+
raise OrientdbError, "unexpeted return code, code=#{response.code}"
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
content_type = response.headers[:content_type]
|
|
490
|
+
content_type ||= 'text/plain'
|
|
491
|
+
|
|
492
|
+
rslt = case
|
|
493
|
+
when content_type.start_with?('text/plain')
|
|
494
|
+
response.body
|
|
495
|
+
when content_type.start_with?('application/json')
|
|
496
|
+
::JSON.parse(response.body)
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
rslt
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def decorate_classes_with_model(classes)
|
|
503
|
+
classes.each do |clazz|
|
|
504
|
+
clazz.extend Orientdb4r::HashExtension
|
|
505
|
+
clazz.extend Orientdb4r::OClass
|
|
506
|
+
unless clazz['properties'].nil? # there can be a class without properties
|
|
507
|
+
clazz.properties.each do |prop|
|
|
508
|
+
prop.extend Orientdb4r::HashExtension
|
|
509
|
+
prop.extend Orientdb4r::Property
|
|
510
|
+
end
|
|
511
|
+
end
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
end
|