vagas-orientdb4r 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +26 -0
  4. data/.travis.yml +18 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE +202 -0
  7. data/README.rdoc +124 -0
  8. data/Rakefile +43 -0
  9. data/changelog.txt +60 -0
  10. data/ci/initialize-ci.sh +36 -0
  11. data/fstudy/design_v1.dia +0 -0
  12. data/fstudy/design_v1.png +0 -0
  13. data/fstudy/domain_model.dia +0 -0
  14. data/fstudy/domain_model.png +0 -0
  15. data/fstudy/flat_class_perf.rb +56 -0
  16. data/fstudy/sample1_object_diagram.dia +0 -0
  17. data/fstudy/sample1_object_diagram.png +0 -0
  18. data/fstudy/study_case.rb +87 -0
  19. data/fstudy/technical_feasibility.rb +256 -0
  20. data/lib/orientdb4r.rb +115 -0
  21. data/lib/orientdb4r/bin/client.rb +86 -0
  22. data/lib/orientdb4r/bin/connection.rb +29 -0
  23. data/lib/orientdb4r/bin/constants.rb +20 -0
  24. data/lib/orientdb4r/bin/io.rb +38 -0
  25. data/lib/orientdb4r/bin/protocol28.rb +101 -0
  26. data/lib/orientdb4r/bin/protocol_factory.rb +25 -0
  27. data/lib/orientdb4r/chained_error.rb +37 -0
  28. data/lib/orientdb4r/client.rb +364 -0
  29. data/lib/orientdb4r/load_balancing.rb +113 -0
  30. data/lib/orientdb4r/node.rb +42 -0
  31. data/lib/orientdb4r/rest/client.rb +517 -0
  32. data/lib/orientdb4r/rest/excon_node.rb +115 -0
  33. data/lib/orientdb4r/rest/model.rb +159 -0
  34. data/lib/orientdb4r/rest/node.rb +43 -0
  35. data/lib/orientdb4r/rest/restclient_node.rb +77 -0
  36. data/lib/orientdb4r/rid.rb +54 -0
  37. data/lib/orientdb4r/utils.rb +203 -0
  38. data/lib/orientdb4r/version.rb +39 -0
  39. data/orientdb4r.gemspec +37 -0
  40. data/test/bin/test_client.rb +21 -0
  41. data/test/readme_sample.rb +38 -0
  42. data/test/test_client.rb +93 -0
  43. data/test/test_database.rb +261 -0
  44. data/test/test_ddo.rb +237 -0
  45. data/test/test_dmo.rb +115 -0
  46. data/test/test_document_crud.rb +184 -0
  47. data/test/test_gremlin.rb +52 -0
  48. data/test/test_helper.rb +10 -0
  49. data/test/test_loadbalancing.rb +81 -0
  50. data/test/test_utils.rb +67 -0
  51. metadata +136 -0
@@ -0,0 +1,364 @@
1
+ require 'uri'
2
+
3
+ module Orientdb4r
4
+
5
+ class Client
6
+ include Utils
7
+
8
+ # # Regexp to validate format of provided version.
9
+ SERVER_VERSION_PATTERN = /^\d+\.\d+\.\d+[-SNAPHOT]*$/
10
+
11
+ # connection parameters
12
+ attr_reader :user, :password, :database
13
+ # type of connection library [:restclient, :excon]
14
+ attr_reader :connection_library
15
+ # type of load balancing [:sequence, :round_robin]
16
+ attr_reader :load_balancing
17
+ # proxy for remote communication
18
+ attr_reader :proxy
19
+
20
+ # intern structures
21
+
22
+ # nodes responsible for communication with a server
23
+ attr_reader :nodes
24
+ # object implementing a LB strategy
25
+ attr_reader :lb_strategy
26
+
27
+ ###
28
+ # Constructor.
29
+ def initialize
30
+ @nodes = []
31
+ @connected = false
32
+ end
33
+
34
+
35
+ # --------------------------------------------------------------- CONNECTION
36
+
37
+ ###
38
+ # Connects client to the server.
39
+ def connect options
40
+ raise NotImplementedError, 'this should be overridden by concrete client'
41
+ end
42
+
43
+
44
+ ###
45
+ # Disconnects client from the server.
46
+ def disconnect
47
+ raise NotImplementedError, 'this should be overridden by concrete client'
48
+ end
49
+
50
+
51
+ ###
52
+ # Gets flag whenever the client is connected or not.
53
+ def connected?
54
+ @connected
55
+ end
56
+
57
+
58
+ ###
59
+ # Retrieve information about the connected OrientDB Server.
60
+ # Enables additional authentication to the server with an account
61
+ # that can access the 'server.info' resource.
62
+ def server(options={})
63
+ raise NotImplementedError, 'this should be overridden by concrete client'
64
+ end
65
+
66
+
67
+ # ----------------------------------------------------------------- DATABASE
68
+
69
+ ###
70
+ # Creates a new database.
71
+ # You can provide an additional authentication to the server with 'database.create' resource
72
+ # or the current one will be used.
73
+ # *options
74
+ # *storage - 'memory' (by default) or 'local'
75
+ # *type - 'document' (by default) or 'graph'
76
+ def create_database(options)
77
+ raise NotImplementedError, 'this should be overridden by concrete client'
78
+ end
79
+
80
+
81
+ ###
82
+ # Retrieves all the information about a database.
83
+ # Client has not to be connected to see databases suitable to connect.
84
+ def get_database(options)
85
+ raise NotImplementedError, 'this should be overridden by concrete client'
86
+ end
87
+
88
+
89
+ ###
90
+ # Checks existence of a given database.
91
+ # Client has not to be connected to see databases suitable to connect.
92
+ def database_exists?(options)
93
+ rslt = true
94
+ begin
95
+ get_database options
96
+ rescue OrientdbError
97
+ rslt = false
98
+ end
99
+ rslt
100
+ end
101
+
102
+
103
+ ###
104
+ # Drops a database.
105
+ # Requires additional authentication to the server.
106
+ def delete_database(options)
107
+ raise NotImplementedError, 'this should be overridden by concrete client'
108
+ end
109
+
110
+
111
+ ###
112
+ # Retrieves the available databases.
113
+ # That is protected by the resource "server.listDatabases"
114
+ # that by default is assigned to the guest (anonymous) user in orientdb-server-config.xml.
115
+ def list_databases(options)
116
+ raise NotImplementedError, 'this should be overridden by concrete client'
117
+ end
118
+
119
+
120
+ ###
121
+ # Exports a gzip file that contains the database JSON export.
122
+ # Returns name of stored file.
123
+ def export(options)
124
+ raise NotImplementedError, 'this should be overridden by concrete client'
125
+ end
126
+
127
+
128
+ ###
129
+ # Imports a database from an uploaded JSON text file.
130
+ def import(options)
131
+ raise NotImplementedError, 'this should be overridden by concrete client'
132
+ end
133
+
134
+ # ---------------------------------------------------------------------- SQL
135
+
136
+ ###
137
+ # Executes a query against the database.
138
+ def query(sql, options)
139
+ raise NotImplementedError, 'this should be overridden by concrete client'
140
+ end
141
+
142
+
143
+ ###
144
+ # Executes a command against the database.
145
+ def command(sql)
146
+ raise NotImplementedError, 'this should be overridden by concrete client'
147
+ end
148
+
149
+
150
+ # -------------------------------------------------------------------- CLASS
151
+
152
+ ###
153
+ # Creates a new class in the schema.
154
+ def create_class(name, options={})
155
+ raise ArgumentError, "class name is blank" if blank?(name)
156
+ opt_pattern = {
157
+ :extends => :optional, :cluster => :optional, :force => false, :abstract => false,
158
+ :properties => :optional
159
+ }
160
+ verify_options(options, opt_pattern)
161
+
162
+ sql = "CREATE CLASS #{name}"
163
+ sql << " EXTENDS #{options[:extends]}" if options.include? :extends
164
+ sql << " CLUSTER #{options[:cluster]}" if options.include? :cluster
165
+ sql << ' ABSTRACT' if options.include?(:abstract)
166
+
167
+ drop_class name if options[:force]
168
+
169
+ command sql
170
+
171
+ # properties given?
172
+ if options.include? :properties
173
+ props = options[:properties]
174
+ raise ArgumentError, 'properties have to be an array' unless props.is_a? Array
175
+
176
+ props.each do |prop|
177
+ raise ArgumentError, 'property definition has to be a hash' unless prop.is_a? Hash
178
+ prop_name = prop.delete :property
179
+ prop_type = prop.delete :type
180
+ create_property(name, prop_name, prop_type, prop)
181
+ end
182
+ end
183
+
184
+ if block_given?
185
+ proxy = Orientdb4r::Utils::Proxy.new(self, name)
186
+ def proxy.property(property, type, options={})
187
+ self.target.send :create_property, self.context, property, type, options
188
+ end
189
+ def proxy.link(property, type, linked_class, options={})
190
+ raise ArgumentError, "type has to be a linked-type, given=#{type}" unless type.to_s.start_with? 'link'
191
+ options[:linked_class] = linked_class
192
+ self.target.send :create_property, self.context, property, type, options
193
+ end
194
+ yield proxy
195
+ end
196
+ end
197
+
198
+
199
+ ###
200
+ # Gets informations about requested class.
201
+ def get_class(name)
202
+ raise NotImplementedError, 'this should be overridden by concrete client'
203
+ end
204
+
205
+
206
+ ###
207
+ # Checks existence of a given class.
208
+ def class_exists?(name)
209
+ rslt = true
210
+ begin
211
+ get_class name
212
+ rescue OrientdbError => e
213
+ raise e if e.is_a? ConnectionError and e.message == 'not connected' # workaround for AOP2 (unable to decorate already existing methods)
214
+ rslt = false
215
+ end
216
+ rslt
217
+ end
218
+
219
+
220
+ ###
221
+ # Removes a class from the schema.
222
+ def drop_class(name, options={})
223
+ raise ArgumentError, 'class name is blank' if blank?(name)
224
+
225
+ # :mode=>:strict forbids to drop a class that is a super class for other one
226
+ opt_pattern = { :mode => :nil }
227
+ verify_options(options, opt_pattern)
228
+ if :strict == options[:mode]
229
+ response = get_database
230
+ children = response['classes'].select { |i| i['superClass'] == name }
231
+ unless children.empty?
232
+ raise OrientdbError, "class is super-class, cannot be deleted, name=#{name}"
233
+ end
234
+ end
235
+
236
+ command "DROP CLASS #{name}"
237
+ end
238
+
239
+
240
+ ###
241
+ # Creates a new property in the schema.
242
+ # You need to create the class before.
243
+ def create_property(clazz, property, type, options={})
244
+ raise ArgumentError, "class name is blank" if blank?(clazz)
245
+ raise ArgumentError, "property name is blank" if blank?(property)
246
+ opt_pattern = {
247
+ :mandatory => :optional , :notnull => :optional, :min => :optional, :max => :optional,
248
+ :readonly => :optional, :linked_class => :optional
249
+ }
250
+ verify_options(options, opt_pattern)
251
+
252
+ cmd = "CREATE PROPERTY #{clazz}.#{property} #{type.to_s}"
253
+ # link?
254
+ if [:link, :linklist, :linkset, :linkmap].include? type.to_s.downcase.to_sym
255
+ raise ArgumentError, "defined linked-type, but not linked-class" unless options.include? :linked_class
256
+ cmd << " #{options[:linked_class]}"
257
+ end
258
+ command cmd
259
+
260
+ # ALTER PROPERTY ...
261
+ options.delete :linked_class # it's not option for ALTER
262
+ unless options.empty?
263
+ options.each do |k,v|
264
+ command "ALTER PROPERTY #{clazz}.#{property} #{k.to_s.upcase} #{v}"
265
+ end
266
+ end
267
+ end
268
+
269
+
270
+ # ----------------------------------------------------------------- DOCUMENT
271
+
272
+ ###
273
+ # Create a new document.
274
+ # Returns the Record-id assigned for OrientDB version <= 1.3.x
275
+ # and the whole new document for version >= 1.4.x
276
+ # (see https://groups.google.com/forum/?fromgroups=#!topic/orient-database/UJGAXYpHDmo for more info).
277
+ def create_document(doc)
278
+ raise NotImplementedError, 'this should be overridden by concrete client'
279
+ end
280
+
281
+
282
+ ###
283
+ # Retrieves a document by given ID.
284
+ def get_document(rid)
285
+ raise NotImplementedError, 'this should be overridden by concrete client'
286
+ end
287
+
288
+
289
+ ###
290
+ # Updates an existing document.
291
+ def update_document(doc)
292
+ raise NotImplementedError, 'this should be overridden by concrete client'
293
+ end
294
+
295
+
296
+ ###
297
+ # Deletes an existing document.
298
+ def delete_document(rid)
299
+ raise NotImplementedError, 'this should be overridden by concrete client'
300
+ end
301
+
302
+
303
+ protected
304
+
305
+ ###
306
+ # Calls the server with a specific task.
307
+ # Returns a response according to communication channel (e.g. HTTP response).
308
+ def call_server(options)
309
+ lb_all_bad_msg = 'all nodes failed to communicate with server!'
310
+ response = nil
311
+
312
+ # credentials if not defined explicitly
313
+ options[:user] = user unless options.include? :user
314
+ options[:password] = password unless options.include? :password
315
+ debug_string = options[:uri]
316
+ if debug_string
317
+ query_log("Orientdb4r::Client#call_server", URI.decode(debug_string))
318
+ end
319
+ idx = lb_strategy.node_index
320
+ raise OrientdbError, lb_all_bad_msg if idx.nil? # no good node found
321
+
322
+ begin
323
+ node = @nodes[idx]
324
+ begin
325
+ response = node.request options
326
+ lb_strategy.good_one idx
327
+ return response
328
+
329
+ rescue NodeError => e
330
+ Orientdb4r::logger.error "node error, index=#{idx}, msg=#{e.message}, #{node}"
331
+ node.cleanup
332
+ lb_strategy.bad_one idx
333
+ idx = lb_strategy.node_index
334
+ end
335
+ end until idx.nil? and response.nil? # both 'nil' <= we tried all nodes and all with problem
336
+
337
+ raise OrientdbError, lb_all_bad_msg
338
+ end
339
+
340
+
341
+ ###
342
+ # Asserts if the client is connected and raises an error if not.
343
+ def assert_connected
344
+ raise ConnectionError, 'not connected' unless @connected
345
+ end
346
+
347
+
348
+ ###
349
+ # Around advice to meassure and print the method time.
350
+ def time_around(&block)
351
+ start = Time.now
352
+ rslt = block.call
353
+ query_log("#{aop_context[:class].name}##{aop_context[:method]}", "elapsed time = #{Time.now - start} [s]")
354
+ rslt
355
+ end
356
+
357
+ def query_log(context, message)
358
+ Orientdb4r::logger.debug \
359
+ " \033[01;33m#{context}:\033[0m #{message}"
360
+ end
361
+
362
+ end
363
+
364
+ end
@@ -0,0 +1,113 @@
1
+ module Orientdb4r
2
+
3
+ ###
4
+ # Base class for implementation of load balancing strategy.
5
+ class LBStrategy
6
+
7
+ # After what time [s] can be a failed node reused in load balancing.
8
+ DEFAULT_RECOVER_TIME = 30
9
+
10
+ attr_reader :nodes_count, :bad_nodes
11
+ attr_accessor :recover_time
12
+
13
+ ###
14
+ # Constructor.
15
+ def initialize nodes_count
16
+ @nodes_count = nodes_count
17
+ @bad_nodes = {}
18
+ @recover_time = DEFAULT_RECOVER_TIME
19
+ end
20
+
21
+ ###
22
+ # Gets index of node to be used for next request
23
+ # or 'nil' if there is no one next.
24
+ def node_index
25
+ raise NotImplementedError, 'this should be overridden in subclass'
26
+ end
27
+
28
+ ###
29
+ # Marks an index as good that means it can be used for next server calls.
30
+ def good_one(idx)
31
+ @bad_nodes.delete idx
32
+ end
33
+
34
+ ###
35
+ # Marks an index as bad that means it will be not used until:
36
+ # * there is other 'good' node
37
+ # * timeout
38
+ def bad_one(idx)
39
+ @bad_nodes[idx] = Time.now
40
+ end
41
+
42
+ protected
43
+
44
+ ###
45
+ # Tries to find a new node if the given failed.
46
+ # Returns <i>nil</i> if no one found
47
+ def search_next_good(bad_idx)
48
+ Orientdb4r::logger.warn "identified bad node, idx=#{bad_idx}, age=#{Time.now - @bad_nodes[bad_idx]} [s]"
49
+
50
+ # alternative nodes of not found a good one
51
+ timeout_candidate = nil
52
+
53
+ # first round - try to find a first good one
54
+ 1.upto(nodes_count) do |i|
55
+ candidate = (i + bad_idx) % nodes_count
56
+
57
+ if @bad_nodes.include? candidate
58
+ failure_time = @bad_nodes[candidate]
59
+ now = Time.now
60
+ # timeout candidate
61
+ if (now - failure_time) > recover_time
62
+ timeout_candidate = candidate
63
+ Orientdb4r::logger.debug "node timeout recovery, idx=#{candidate}"
64
+ good_one(candidate)
65
+ end
66
+ else
67
+ Orientdb4r::logger.debug "found good node, idx=#{candidate}"
68
+ return candidate
69
+ end
70
+ end
71
+
72
+ # no good index found -> try timeouted one
73
+ unless timeout_candidate.nil?
74
+ Orientdb4r::logger.debug "good node not found, delivering timeouted one, idx=#{timeout_candidate}"
75
+ return timeout_candidate
76
+ end
77
+
78
+ Orientdb4r::logger.error 'no nodes more, all invalid'
79
+ nil
80
+ end
81
+
82
+ end
83
+
84
+ ###
85
+ # Implementation of Sequence strategy.
86
+ # Assigns work in the order of nodes defined by the client initialization.
87
+ class Sequence < LBStrategy
88
+
89
+ def node_index #:nodoc:
90
+ @last_index = 0 if @last_index.nil?
91
+
92
+ @last_index = search_next_good(@last_index) if @bad_nodes.include? @last_index
93
+ @last_index
94
+ end
95
+
96
+ end
97
+
98
+ ###
99
+ # Implementation of Round Robin strategy.
100
+ # Assigns work in round-robin order per nodes defined by the client initialization.
101
+ class RoundRobin < LBStrategy
102
+
103
+ def node_index #:nodoc:
104
+ @last_index = -1 if @last_index.nil?
105
+
106
+ @last_index = (@last_index + 1) % nodes_count
107
+ @last_index = search_next_good(@last_index) if @bad_nodes.include? @last_index
108
+ @last_index
109
+ end
110
+
111
+ end
112
+
113
+ end