active-orient 0.42 → 0.79

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/Gemfile +13 -5
  4. data/Guardfile +12 -4
  5. data/README.md +67 -280
  6. data/VERSION +1 -1
  7. data/active-orient.gemspec +6 -5
  8. data/bin/active-orient-0.6.gem +0 -0
  9. data/bin/active-orient-console +85 -0
  10. data/config/boot.rb +72 -1
  11. data/config/config.yml +10 -0
  12. data/config/connect.yml +9 -4
  13. data/examples/books.rb +92 -40
  14. data/examples/streets.rb +89 -85
  15. data/examples/test_commands.rb +97 -0
  16. data/examples/test_commands_2.rb +59 -0
  17. data/examples/test_commands_3.rb +55 -0
  18. data/examples/test_commands_4.rb +33 -0
  19. data/examples/time_graph.md +162 -0
  20. data/lib/active-orient.rb +75 -9
  21. data/lib/base.rb +238 -169
  22. data/lib/base_properties.rb +68 -60
  23. data/lib/class_utils.rb +226 -0
  24. data/lib/database_utils.rb +98 -0
  25. data/lib/init.rb +79 -0
  26. data/lib/java-api.rb +442 -0
  27. data/lib/jdbc.rb +211 -0
  28. data/lib/model/custom.rb +26 -0
  29. data/lib/model/edge.rb +70 -0
  30. data/lib/model/model.rb +134 -0
  31. data/lib/model/the_class.rb +607 -0
  32. data/lib/model/the_record.rb +266 -0
  33. data/lib/model/vertex.rb +236 -0
  34. data/lib/orientdb_private.rb +48 -0
  35. data/lib/other.rb +371 -0
  36. data/lib/railtie.rb +68 -0
  37. data/lib/rest/change.rb +147 -0
  38. data/lib/rest/create.rb +279 -0
  39. data/lib/rest/delete.rb +134 -0
  40. data/lib/rest/operations.rb +211 -0
  41. data/lib/rest/read.rb +171 -0
  42. data/lib/rest/rest.rb +112 -0
  43. data/lib/rest_disabled.rb +24 -0
  44. data/lib/support/logging.rb +38 -0
  45. data/lib/support/orient.rb +196 -0
  46. data/lib/support/orientquery.rb +469 -0
  47. data/rails.md +154 -0
  48. data/rails/activeorient.rb +32 -0
  49. data/rails/config.yml +10 -0
  50. data/rails/connect.yml +17 -0
  51. metadata +65 -24
  52. data/active-orient-0.4.gem +0 -0
  53. data/active-orient-0.41.gem +0 -0
  54. data/lib/model.rb +0 -468
  55. data/lib/orient.rb +0 -98
  56. data/lib/query.rb +0 -88
  57. data/lib/rest.rb +0 -1059
  58. data/lib/support.rb +0 -372
  59. data/test.rb +0 -4
  60. data/usecase.md +0 -91
@@ -1,98 +0,0 @@
1
- module OrientSupport
2
- =begin
3
- This Module fences specialized ruby objects
4
- =end
5
-
6
- class Array < Array
7
- include OrientSupport::Support
8
- =begin
9
- Initialisation method stores the modelinstance in @orient.
10
-
11
- Further a list of array-elements is expected, which are forwarded (as Array) to Array
12
-
13
-
14
- =end
15
- def initialize modelinstance, *args
16
- @orient = modelinstance
17
- super args
18
- @name = modelinstance.attributes.key(self)
19
-
20
- end
21
-
22
- def << arg
23
- @orient.add_item_to_property( @name, arg ) if @name.present?
24
- super
25
- end
26
-
27
- =begin
28
- Updating of single items
29
-
30
- this only works if the hole embedded Array is previosly loaded into the ruby-array.
31
- =end
32
-
33
- def []= key, value
34
- super
35
- @orient.update set: { @name => self } if @name.present?
36
- end
37
-
38
- def [] *arg
39
- # puts "[] ARG: #{arg.inspect}"
40
- # arg.each{|u| puts "[] #{u.inspect} : #{u.class} " }
41
- super
42
-
43
- end
44
-
45
- def delete_at *pos
46
- if @name.present?
47
- delete self[*pos]
48
- else
49
- super
50
- end
51
- # old version: works only if the hole array is loaded into memory
52
- # self[*pos]=nil
53
- # @orient.update set:{ @name => self.compact }
54
- end
55
-
56
- def delete_if
57
- if @name.present?
58
- delete *self.map{|y| y if yield(y) }.compact # if the block returns true then delete the item
59
- else
60
- super
61
- end
62
- end
63
-
64
- def delete *item
65
- @orient.remove_item_from_property( @name ) { item } if @name.present?
66
- end
67
-
68
- def where *item
69
- where_string = item.map{|m| where_string = compose_where m }.join( ' and ' )
70
- query = "select from ( select expand( #{@name} ) from #{@orient.classname}) #{where_string} "
71
- puts query
72
- @orient.query query
73
-
74
- end
75
-
76
- def method_missing *args
77
- map{|x| x.send args.first }
78
- end
79
- end
80
- class LinkMap < OrientSupport::Array
81
-
82
- def []= arg
83
- end
84
- end
85
-
86
- #
87
- # class Hash < Hash_with_indifferent_access
88
- # # additional and overlayed methods for Hash-Objects in OrientDB
89
- # def initialize modelinstance, *args
90
- # @orient = modelinstance
91
- # super args
92
- # @name = modelinstance.attributes.key(self)
93
- #
94
- # end
95
- #
96
- # end
97
-
98
- end
@@ -1,88 +0,0 @@
1
-
2
- module ActiveOrient
3
- class Query < ActiveOrient::Model
4
-
5
- has_many :records
6
- has_many :queries
7
-
8
- def reset_records
9
- self.records= []
10
- end
11
- alias reset_results reset_records
12
-
13
- def reset_queries
14
- self.queries = []
15
- end
16
- =begin
17
- calls ActiveOrient::ActiveOrient#GetDocuments
18
- stores the query in the query-stack and saves the result in the record-Array
19
-
20
- returns the count of assigned records
21
- =end
22
-
23
- def get_documents o_class , **args
24
-
25
- query = OrientSupport::OrientQuery.new class_name(o_class), args
26
- self.queries << query.compose
27
- count= 0
28
- orientdb.get_documents( o_class , query: query.compose ).each{|c| records << c; count+=1 }
29
- count
30
- end
31
-
32
- =begin
33
- All predefined queries are send to the database.
34
- The result is stored in the records.
35
- Unknown Records are of Type ActiveOrient::Model::Myquery, uses ActiveOrient::Orientdb.execute which tries to autosuggest the ActiveOrient::Model::{Class}
36
-
37
- example: Multible Records
38
- ach = ActiveOrient::Query.new
39
- ach.queries << 'create class Contracts ABSTRACT'
40
- ach.queries << 'create property Contracts.details link'
41
- ach.queries << 'create class Stocks extends Contracts'
42
- result = ach.execute_queries transaction: false
43
-
44
- example: Batch
45
- q = ActiveOrient::Query.new
46
- q.queries << [
47
- "select expand( contracts ) from Openinterest"
48
- "let con = select expand( contracts ) from Openinterest; ",
49
- "let sub = select from Subcategories where contracts in $con;",
50
- "let cat = select from Categories where subcategories in $sub;",
51
- "let ind = select from Industries where categories in $cat;",
52
- "SELECT expand(unionall) FROM (SELECT unionall( $con, $cat))"
53
- ]
54
- q.execute_queries.each{|x| puts "X #{x.inspect}" }
55
-
56
- =end
57
- def execute_queries reset: true, transaction: true
58
- reset_records if reset
59
- begin
60
- orientdb.execute( transaction: transaction ) do
61
- result = queries.map do |q|
62
- # command: words are seperated by one space only, thus squeeze multible spaces
63
- sql_cmd = -> (command) { { type: "cmd", language: "sql", command: command.squeeze(' ') } }
64
- batch_cmd = ->( command_array ){ {type: "script", language: "sql", script: command_array } }
65
- case q
66
- when String
67
- sql_cmd[ q ]
68
- when Hash
69
- q
70
- when Array
71
- batch_cmd[ q ]
72
- else
73
- nil
74
- end # case
75
- end.compact
76
- # save the result in records
77
- result.each{|y| records << y }
78
-
79
- end # block
80
- rescue RestClient::InternalServerError => e
81
- puts e.inspect
82
- end
83
-
84
- end # def execute_queries
85
-
86
- end # class
87
-
88
- end # module
@@ -1,1059 +0,0 @@
1
- module ActiveOrient
2
- require 'cgi'
3
- require 'rest-client'
4
- require 'active_support/core_ext/string' # provides blank?, present?, presence etc
5
- require 'model'
6
- require 'model'
7
- =begin
8
- OrientDB performs queries to a OrientDB-Database
9
-
10
- The communication is based on the ActiveOrient-API.
11
-
12
- The OrientDB-Server is specified in config/connect.yml
13
-
14
- A Sample:
15
- :orientdb:
16
- :server: localhost
17
- :port: 2480
18
- :database: working-database
19
- :admin:
20
- :user: admin-user
21
- :pass: admin-password
22
-
23
-
24
- =end
25
-
26
- class OrientDB
27
- mattr_accessor :logger ## borrowed from active_support
28
- mattr_accessor :default_server ##
29
- ## expected
30
- # ActiveOrient::OrientDB.default_server = { server: 'localhost', port: 2480, user: '**', password: '**', database: 'temp'
31
- include OrientSupport::Support
32
-
33
- =begin
34
- Contructor: OrientDB is conventionally initialized.
35
- Thus several instances pointing to the same or different databases can coexist
36
-
37
- A simple
38
- xyz = ActiveOrient::OrientDB.new
39
- uses the database specified in the yaml-file »config/connect.yml« and connects
40
- xyz = ActiveOrient::OrientDB.new database: my_fency_database
41
- accesses the database »my_fency_database«. The database is created if its not existing.
42
-
43
- *USECASE*
44
- xyz = ActiveOrient::Model.orientdb = ActiveOrient::OrientDB.new
45
- initialises the Database-Connection and publishes the Instance to any ActiveOrient::Model-Object
46
- =end
47
-
48
-
49
- def initialize database: nil, connect: true
50
- self.default_server = { :server => 'localhost', :port => 2480, :protocol => 'http',
51
- :user => 'root', :password => 'root', :database => 'temp' }.merge default_server
52
- @res = get_ressource
53
- @database = database.presence || default_server[:database]
54
- self.logger = Logger.new('/dev/stdout') unless logger.present?
55
- # @database=@database#.camelcase
56
- connect() if connect
57
- # save existing classes
58
- @classes = []
59
- ActiveOrient::Model.orientdb = self
60
- end
61
-
62
- ## included for development , should be removed in production release
63
- def ressource
64
- @res
65
- end
66
- ##
67
-
68
- # called in the beginning or after a 404-Error
69
- def get_ressource
70
- login = [ default_server[:user].to_s , default_server[:password].to_s ]
71
- server_adress = [ default_server[:protocol] ,"://" , default_server[ :server ], ":" , default_server[ :port ]].map(&:to_s).join('')
72
- RestClient::Resource.new( server_adress, *login )
73
- end
74
-
75
- def connect
76
- i = 0
77
- begin
78
- logger.progname = 'OrientDB#Connect'
79
- r= @res["/connect/#{ @database }" ].get
80
- if r.code == 204
81
- logger.info{ "Connected to database #{@database} " }
82
- true
83
- else
84
- logger.error{ "Connection to database #{@database} could NOT be established" }
85
- nil
86
- end
87
- rescue RestClient::Unauthorized => e
88
- if i.zero?
89
- logger.info{ "Database #{@database} NOT present --> creating" }
90
- i=i+1
91
- create_database
92
- retry
93
- else
94
- Kernel.exit
95
- end
96
- end
97
-
98
- end
99
-
100
- ## -----------------------------------------------------------------------------------------
101
- ##
102
- ## Database stuff
103
- ##
104
- ## get_databases
105
- ## create_database
106
- ## change_database
107
- ## delete_database
108
- ## -----------------------------------------------------------------------------------------
109
-
110
- =begin
111
- returns an Array with available Database-Names as Elements
112
- =end
113
- def get_databases
114
- JSON.parse( @res["/listDatabases"].get.body)['databases']
115
- end
116
-
117
- =begin
118
- Creates a database with the given name and switches to this database as working-database
119
- Types are either 'plocal' or 'memory'
120
-
121
- returns the name of the working-database
122
- =end
123
- def create_database type: 'plocal' , database: @database
124
- logger.progname = 'OrientDB#CreateDatabase'
125
- old_d = @database
126
- @classes = []
127
- @database = database
128
- begin
129
- response = @res[database_uri{ type }].post ""
130
- if response.code == 200
131
- logger.info{ "Database #{@database} successfully created and stored as working database"}
132
- else
133
- @database = old_d
134
- logger.error{ "Database #{name} was NOT created. Working Database is still #{@database} "}
135
- end
136
- rescue RestClient::InternalServerError => e
137
- @database = old_d
138
- logger.error{ "Database #{name} was NOT created. Working Database is still #{@database} "}
139
- end
140
- @database
141
-
142
- end
143
- =begin
144
- changes the working-database to {name}
145
- =end
146
- def change_database name
147
- @classes = []
148
- @database = name
149
-
150
- end
151
- =begin
152
- deletes the database and returns true on success
153
-
154
- after the removal of the database, the working-database might be empty
155
- =end
156
- def delete_database database:
157
- @classes = []
158
- logger.progname = 'OrientDB#DropDatabase'
159
- old_ds = @database
160
- change_database database
161
- begin
162
- response = @res[database_uri].delete
163
- if database == old_ds
164
- change_database ""
165
- logger.info{ "Working database deleted" }
166
- else
167
- change_database old_ds
168
- logger.info{ "Database #{database} deleted, working database is still #{@database} "}
169
- end
170
- rescue RestClient::InternalServerError => e
171
- logger.info{ "Database #{database} NOT deleted" }
172
- change_database old_ds
173
- end
174
- !response.nil? && response.code == 204 ? true : false
175
-
176
- end
177
-
178
- ## -----------------------------------------------------------------------------------------
179
- ##
180
- ## Inspect, Create and Delete Classes
181
- ##
182
- ## inspect_classes
183
- ## create_class
184
- ## delete_class
185
- ##
186
- ## -----------------------------------------------------------------------------------------
187
-
188
- =begin
189
- returns an Array with (unmodified) Class-attribute-hash-Elements
190
-
191
- get_classes 'name', 'superClass'
192
- returns
193
- [ {"name"=>"E", "superClass"=>""},
194
- {"name"=>"OFunction", "superClass"=>""},
195
- {"name"=>"ORole", "superClass"=>"OIdentity"}
196
- (...)
197
- ]
198
- =end
199
-
200
- def get_classes *attributes
201
- i = 0
202
- begin
203
- #puts "database_uri #{database_uri}"
204
- response = @res[database_uri].get
205
- if response.code == 200
206
- classes= JSON.parse( response.body )['classes' ]
207
- unless attributes.empty?
208
- classes.map{|y| y.select{| v,_| attributes.include?(v) } }
209
- else
210
- classes
211
- end
212
-
213
- #.map{ |y| y.name }
214
- else
215
- []
216
- end
217
- rescue JSON::ParserError
218
- if i.zero?
219
- i = i + 1
220
- retry
221
- else
222
- raise
223
- end
224
- end
225
-
226
- end
227
- =begin
228
- returns the class_hierachie
229
-
230
- to fetch all Vertices
231
- class_hiearchie( base_class: 'V').flatten
232
- to fetch all Edges
233
- class_hierachie( base_class: 'E').flatten
234
-
235
- Notice: base_class has to be noted as String! There is no implicit conversion from Symbol or Class
236
- =end
237
-
238
- def class_hierachie base_class: '', requery: false
239
- @all_classes = get_classes( 'name', 'superClass') if requery || @all_classes.blank?
240
- def fv s # :nodoc:
241
- @all_classes.find_all{ |x| x[ 'superClass' ]== s }.map{| v| v[ 'name' ]} #.camelize }
242
- end
243
-
244
- def fx v # :nodoc:
245
- fv(v).map{ |x| ar = fx( x ) ; ar.empty? ? x : [ x , ar ] }
246
- end
247
-
248
- fx base_class
249
- end
250
- =begin
251
- returns an array with all names of the classes of the database
252
- caches the result.
253
-
254
- parameter: include_system_classes: false|true, requery: false|true
255
- =end
256
- def database_classes include_system_classes: false, requery: false
257
- requery = true if @classes.empty?
258
- if requery
259
- class_hierachie requery: true
260
- system_classes = ["OFunction", "OIdentity", "ORIDs", "ORestricted", "ORole", "OSchedule", "OTriggered", "OUser", "_studio"]
261
- all_classes = get_classes( 'name' ).map( &:values).flatten
262
- @classes = include_system_classes ? all_classes : all_classes - system_classes
263
- end
264
- @classes
265
- end
266
-
267
- alias inspect_classes database_classes
268
-
269
- =begin
270
- Creates classes and class-hierachies in OrientDB and in Ruby.
271
-
272
-
273
- Takes an Array or a Hash as argument and returns an Array of
274
- successfull allocated Ruby-Classes
275
-
276
- If the argument is an array, Basic-Classes are build.
277
-
278
- Otherwise key/value pairs are assumend to follow this terminology
279
- { SuperClass => [ class, class, ...], SuperClass => [] , ... }
280
- =end
281
-
282
- def create_classes classes
283
- # rebuild cashed classes-array
284
- # first the classes-string is camelized (this is used to allocate the ruby-class)
285
- # Then the database is queried for this string or the underscored string-variant
286
- # the name-building process is independend from the method »class_name«
287
- database_classes requery: true
288
- consts = Array.new
289
- execute transaction: false do
290
- class_cmd = ->(s,n) do
291
- n = n.to_s.camelize
292
- consts << ActiveOrient::Model.orientdb_class( name: n)
293
- unless database_classes.include?(n) || database_classes.include?(n.underscore)
294
- { type: "cmd", language: 'sql', command: "create class #{n} extends #{s}" }
295
-
296
- end
297
- end ## class_cmd
298
-
299
- if classes.is_a?(Array)
300
- classes.map do | n |
301
- n = n.to_s.camelize # capitalize
302
- consts << ActiveOrient::Model.orientdb_class( name: n)
303
- unless database_classes.include?( n ) || database_classes.include?(n.underscore)
304
- { type: "cmd", language: 'sql', command: "create class #{n} " }
305
- end
306
- end
307
- elsif classes.is_a?(Hash)
308
- classes.keys.map do | superclass |
309
- items = Array.new
310
- superClass = superclass.to_s.camelize
311
- items << { type: "cmd", language: 'sql', command: "create class #{superClass} abstract" } unless database_classes.flatten.include?( superClass ) || database_classes.flatten.include?( superClass.underscore )
312
- items << if classes[superclass].is_a?( String ) || classes[superclass].is_a?( Symbol )
313
- class_cmd[superClass, classes[superclass] ]
314
- elsif classes[superclass].is_a?( Array )
315
- classes[superclass].map{|n| class_cmd[superClass, n] }
316
- end
317
- #puts items.flatten.map{|x| x[:command]}
318
- items # returnvalue
319
- end.flatten
320
- end.compact # erase nil-entries, in case the class is already allocated
321
- end
322
- # refresh cached class-informations
323
- database_classes requery: true
324
- # returns an array of allocated Constants/Classes
325
- consts
326
- end
327
-
328
- =begin
329
- creates a class and returns the a ActiveOrient::Model:{Newclass}-Class- (Constant)
330
- which is designed to take any documents stored in this class
331
-
332
- Predefined attributes: version, cluster, record
333
-
334
- Other attributes are assigned dynamically upon reading documents
335
-
336
- The classname is Camelized by default, eg: classnames are always Capitalized, underscores ('_') indicate
337
- that the following letter is capitalized, too.
338
-
339
- Other class-names can be used if a "$" is placed at the begining of the name-string. However, most of the
340
- model-based methods will not work in this case.
341
- =end
342
- def create_class newclass
343
- create_classes( [ newclass ] ).first
344
- end
345
-
346
- alias open_class create_class
347
-
348
- def create_vertex_class name , superclass: 'V'
349
- create_classes( { superclass => name } ).first
350
- end
351
-
352
- def create_edge_class name , superclass: 'E'
353
- create_classes( { superclass => name } ).first
354
- end
355
-
356
- =begin
357
- nexus_edge connects two documents/vertexes
358
- The parameter o_class can be either a class or a string
359
- =end
360
-
361
- def nexus_edge o_class , attributes: {}, from:, to:, unique: false
362
- logger.progname = "ActiveOrient::OrientDB#NexusEdge"
363
- if from.is_a? Array
364
- from.map{|f| nexus_edge o_class, attributes: attributes, from: f, to: to, unique: unique }
365
- elsif to.is_a? Array
366
- to.map{|t| nexus_edge o_class, attributes: attributes, from: from, to: t, unique: unique }
367
- else
368
-
369
- if unique
370
- wwhere = { out: from.to_orient, in: to.to_orient }.merge(attributes.to_orient)
371
- existing_edge = get_documents( from: o_class, where: wwhere )
372
- if existing_edge.first.is_a?( ActiveOrient::Model )
373
- logger.debug { "reusing edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
374
- return existing_edge.first
375
- end
376
- end
377
- logger.debug { "creating edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} " }
378
- response= execute( o_class, transaction: false) do
379
- #[ { type: "cmd", language: 'sql', command: CGI.escapeHTML("create edge #{class_name(o_class)} from #{translate_to_rid[m]} to #{to.to_orient}; ")} ]
380
- attr_string = attributes.blank? ? "" : "set #{ generate_sql_list attributes.to_orient }"
381
- [ { type: "cmd", language: 'sql',
382
- command: "create edge #{class_name(o_class)} from #{from.to_orient} to #{to.to_orient} #{attr_string}"} ]
383
- end
384
- if response.is_a?(Array) && response.size == 1
385
- response.pop # RETURN_VALUE
386
- else
387
- response
388
- end
389
- end
390
- end
391
-
392
- =begin
393
- Deletes a single edge when providing a single rid-link (#00:00)
394
- Deletes multible edges when providing a list of rid-links
395
- Todo: implement delete_edges after querying the database in one statement
396
-
397
- =end
398
- def delete_edge *rid
399
- rid = rid.map do |mm|
400
- if mm.is_a?(String)
401
- if mm.rid?
402
- mm
403
- elsif mm.is_a?(Rest::Model)
404
- mm.rid
405
- else
406
- nil
407
- end
408
- end
409
- end.compact
410
- response= execute transaction: false do
411
- [ { type: "cmd", language: 'sql', command: CGI.escapeHTML("delete edge #{rid.join(',') }")} ]
412
- end
413
- if response.is_a?( Array ) && response.size == 1
414
- response.pop # RETURN_VALUE
415
- else
416
- response
417
- end
418
- end
419
- =begin
420
- deletes the specified class and returns true on success
421
-
422
-
423
- todo: remove all instances of the class
424
- =end
425
- def delete_class o_class
426
- cl= class_name(o_class)
427
- logger.progname = 'OrientDB#DeleteClass'
428
-
429
- if database_classes.include? cl
430
- begin
431
- response = @res[class_uri{ cl } ].delete
432
- if response.code == 204
433
- # return_value: sussess of the removal
434
- !database_classes( requery: true ).include?(cl)
435
- # don't delete the ruby-class
436
- # ActiveOrient::Model.send :remove_const, cl.to_sym if o_class.is_a?(Class)
437
- end
438
- rescue RestClient::InternalServerError => e
439
- if database_classes( requery: true).include?( cl )
440
- logger.error{ "Class #{cl} still present" }
441
- logger.error{ e.inspect }
442
- false
443
- else
444
- true
445
- end
446
- end
447
- else
448
- logger.info { "Class #{cl} not present. "}
449
- end
450
-
451
- end
452
- =begin
453
- create a single property on class-level.
454
-
455
- supported types: https://orientdb.com/docs/last/SQL-Create-Property.html
456
-
457
- if index is to be specified, it's defined in the optional block
458
- create_property(class, field){ :unique | :notunique } --> creates an automatic-Index on the given field
459
- create_property( class, field){ { »name« => :unique | :notunique | :full_text } } --> creates a manual index
460
-
461
- =end
462
- def create_property o_class, field, index: nil, **args
463
- logger.progname= 'OrientDB#CreateProperty'
464
- c= create_properties o_class, {field => args} # translate_property_hash( field, args )
465
- if index.nil? && block_given?
466
- index = yield
467
- end
468
- if c==1 && index.present?
469
- if index.is_a?( String ) || index.is_a?( Symbol )
470
- create_index o_class, name: field, type: index
471
- elsif index.is_a? Hash
472
- bez= index.keys.first
473
- # puts "index: #{index.inspect}"
474
- # puts "bez: #{bez} ---> #{index.keys.inspect}"
475
- create_index o_class, name: bez, type: index[bez], on: [ field ]
476
- end
477
- end
478
-
479
- end
480
-
481
-
482
- def create_index o_class , name:, on: :automatic, type: :unique
483
-
484
- execute transaction: false do
485
- c = class_name o_class
486
- command = if on == :automatic
487
- "create index #{c}.#{name} #{type.to_s.upcase}"
488
- elsif on.is_a? Array
489
- "create index #{name} on #{class_name(o_class)}( #{on.join(', ')}) #{type.to_s.upcase}"
490
- else
491
- nil
492
- end
493
- [ { type: "cmd", language: 'sql', command: command } ]
494
- end
495
-
496
- end
497
- ## -----------------------------------------------------------------------------------------
498
- ##
499
- ## Properties
500
- ##
501
- ## create_properties
502
- ## get_class_properties
503
- ## delete_properties
504
-
505
- ##
506
- ## -----------------------------------------------------------------------------------------
507
- private
508
- def translate_property_hash field, type: nil, linked_class: nil, **args
509
- type = type.presence || args[:propertyType].presence || args[:property_type]
510
- linked_class = linked_class.presence || args[:linkedClass]
511
- if type.present?
512
- if linked_class.nil?
513
- { field => { propertyType: type.to_s.upcase } }
514
- else
515
- { field => { propertyType: type.to_s.upcase, linkedClass: class_name( linked_class ) } }
516
- end
517
- end
518
-
519
- end
520
- public
521
- =begin
522
-
523
- creates properties and optional an associated index as defined in the provided block
524
-
525
- create_properties( classname or class, properties as hash ) { index }
526
-
527
- The default-case
528
-
529
- create_properties( :my_high_sophisticated_database_class,
530
- con_id: { type: :integer },
531
- details: { type: :link, linked_class: 'Contracts' } ) do
532
- contract_idx: :notunique
533
- end
534
-
535
- A composite index
536
-
537
- create_properties( :my_high_sophisticated_database_class,
538
- con_id: { type: :integer },
539
- symbol: { type: :string } ) do
540
- { name: 'indexname',
541
- on: [ :con_id , :details ] # default: all specified properties
542
- type: :notunique # default: :unique
543
- }
544
- end
545
-
546
- =end
547
- def create_properties o_class, all_properties, &b
548
-
549
- all_properties_in_a_hash = HashWithIndifferentAccess.new
550
- all_properties.each{| field, args | all_properties_in_a_hash.merge! translate_property_hash( field, args ) }
551
-
552
- begin
553
- count = if all_properties_in_a_hash.is_a?( Hash )
554
- response = @res[ property_uri(class_name(o_class)) ].post all_properties_in_a_hash.to_json
555
- if response.code == 201
556
- response.body.to_i
557
- else
558
- 0
559
- end
560
- end
561
- rescue RestClient::InternalServerError => e
562
- response = JSON.parse( e.response)['errors'].pop
563
- error_message = response['content'].split(':').last
564
- # if error_message ~= /Missing linked class/
565
- logger.progname= 'OrientDB#CreatePropertes'
566
- logger.error { "Properties in #{class_name(o_class)} were NOT created" }
567
- logger.error { "Error-code #{response['code']} --> #{response['content'].split(':').last }" }
568
- nil
569
- end
570
- ### index
571
- if block_given? && count == all_properties_in_a_hash.size
572
- index = yield
573
- if index.is_a?( Hash )
574
- if index.size == 1
575
- create_index o_class, name: index.keys.first, on: all_properties_in_a_hash.keys, type: index.values.first
576
- else
577
- index_hash = HashWithIndifferentAccess.new( type: :unique, on: all_properties_in_a_hash.keys ).merge index
578
- create_index o_class, index_hash # i [:name], on: index_hash[:on], type: index_hash[:type]
579
- end
580
- end
581
- end
582
- count # return_value
583
- end
584
-
585
- def delete_property o_class, field
586
- logger.progname = 'OrientDB#DeleteProperty'
587
- begin
588
- response = @res[property_uri( class_name(o_class)){ field } ].delete
589
- true if response.code == 204
590
- rescue RestClient::InternalServerError => e
591
- logger.error{ "Property #{ field } in class #{ class_name(o_class) } NOT deleted" }
592
- false
593
- end
594
-
595
- end
596
-
597
- def get_class_properties o_class # :nodoc:
598
- JSON.parse( @res[ class_uri{ class_name(o_class) } ].get )
599
- end
600
-
601
- def print_class_properties o_class
602
- puts "Detected Properties for class #{class_name(o_class)}"
603
-
604
- rp = get_class_properties o_class
605
- n= rp['name']
606
- puts rp['properties'].map{|x| [ n+'.'+x['name'], x['type'],x['linkedClass'] ].compact.join(' -> ' )}.join("\n")
607
-
608
- end
609
- #
610
- ## -----------------------------------------------------------------------------------------
611
- ##
612
- ## Documents
613
- ##
614
- ## create_document get_document delete_document
615
- ## update_or_create_document patch_document
616
- ##
617
- ##
618
- ## get_documents
619
- ## update_documents
620
- ## delete_documents
621
- ## update_or_create_documents
622
- ## -----------------------------------------------------------------------------------------
623
-
624
- =begin #nodoc#
625
- If properties are allocated on class-level, they can be preinitialized using
626
- this method.
627
- This is disabled for now, because it does not seem nessesary
628
-
629
- =end
630
- def preallocate_class_properties o_class
631
- p= get_class_properties( o_class )['properties']
632
- unless p.nil? || p.blank?
633
- predefined_attributes = p.map do | property |
634
- [ property['name'] ,
635
- case property['type']
636
- when 'LINKMAP'
637
- Array.new
638
- when 'STRING'
639
- ''
640
- else
641
- nil
642
- end ]
643
- end.to_h
644
- else
645
- {}
646
- end
647
- end
648
-
649
- =begin
650
- Creates an Object in the Database and returns this as ActuveOrient::Model-Instance
651
- =end
652
-
653
- def create_document o_class, attributes: {}
654
- attributes = yield if attributes.empty? && block_given?
655
- post_argument = { '@class' => class_name(o_class) }.merge(attributes).to_orient
656
-
657
- begin
658
- logger.progname = 'OrientDB#CreateDocument'
659
- response = @res[ document_uri ].post post_argument.to_json
660
- data= JSON.parse( response.body )
661
- ActiveOrient::Model.orientdb_class( name: data['@class']).new data
662
- rescue RestClient::InternalServerError => e
663
- response = JSON.parse( e.response)['errors'].pop
664
- logger.error { response['content'].split(':')[1..-1].join(':') }
665
- logger.error { "No Object allocated" }
666
- nil # return_value
667
- end
668
- end
669
-
670
-
671
- def delete_document record_id
672
- logger.progname = 'OrientDB#DeleteDocument'
673
- begin
674
- response = @res[document_uri{ record_id } ].delete
675
- true if response.code == 204
676
- rescue RestClient::InternalServerError => e
677
- logger.error{ "Document #{ record_id } NOT deleted" }
678
- false
679
- end
680
-
681
- end
682
- =begin
683
- retrieves documents from a query
684
-
685
- If raw is specified, the JSON-Array is returned, eg
686
- { "@type"=>"d",
687
- "@rid"=>"#15:1",
688
- "@version"=>1,
689
- "@class"=>"DocumebntKlasse10",
690
- "con_id"=>343,
691
- "symbol"=>"EWTZ"
692
- }
693
- otherwise a ActiveModel-Instance of o_class is created and returned
694
- =end
695
- def get_documents raw: false, query: nil, **args
696
- query = OrientSupport::OrientQuery.new( args ) if query.nil?
697
- i=0
698
- begin
699
- logger.progname = 'OrientDB#GetDocuments'
700
- url = query_sql_uri + query.compose( destination: :rest) + "/#{query.get_limit}"
701
- response = @res[URI.encode(url) ].get
702
- r=JSON.parse( response.body )['result'].map do |document |
703
- # parameter: raw is set --> don't initilize a model object
704
- if raw
705
- document
706
- # query returns an anonymus class: Use the provided Block or the Dummy-Model MyQuery
707
- elsif document['@class'].blank?
708
- block_given? ? yield.new( document ) : ActiveOrient::Model::MyQuery.new( document )
709
- else
710
- ActiveOrient::Model.orientdb_class( name: document['@class']).new document
711
- end
712
- end
713
- rescue RestClient::InternalServerError => e
714
- response = JSON.parse( e.response)['errors'].pop
715
- logger.error { response['content'].split(':').last }
716
- i=i+1
717
- if i > 1
718
- raise
719
- else
720
- query.dataset_name = query.database_class.underscore
721
- logger.info { "trying to query using #{o_class}" }
722
- retry
723
- end
724
- rescue URI::InvalidURIError => e
725
- logger.error "Invalid URI detected"
726
- logger.error query.to_s
727
- logger.info "trying batch processing "
728
- sql_cmd = -> (command) { { type: "cmd", language: "sql", command: command } }
729
- response= execute { [ sql_cmd[ query.to_s ] ] }
730
- logger.info "success: to avoid this delay use ActiveOrient::Model#query_database insteed"
731
- response
732
- end
733
-
734
- end
735
-
736
- def count_documents **args
737
- logger.progname = 'OrientDB#count_documents'
738
-
739
- query = OrientSupport::OrientQuery.new args
740
- query.projection << 'COUNT (*)'
741
- result = get_documents raw: true, query: query
742
-
743
- result.first['COUNT']
744
-
745
- end
746
-
747
- =begin
748
- Inserts a Document with the attributes provided in the attributes-hash
749
- eg
750
- create_document class_name: @classname, attributes: { con_id: 343, symbol: 'EWTZ' }
751
-
752
- untested: for hybrid and schema-less documents the following syntax is supported
753
- create_document class_name: "Account",
754
- attributes: { date: 1350426789, amount: 100.34,
755
- "@fieldTypes" => "date=t,amount=c" }
756
-
757
- The supported special types are:
758
- n
759
- 'f' for float
760
- 'c' for decimal
761
- 'l' for long
762
- 'd' for double
763
- 'b' for byte and binary
764
- 'a' for date
765
- 't' for datetime
766
- 's' for short
767
- 'e' for Set, because arrays and List are serialized as arrays like [3,4,5]
768
- 'x' for links
769
- 'n' for linksets
770
- 'z' for linklist
771
- 'm' for linkmap
772
- 'g' for linkbag
773
-
774
- =end
775
- =begin
776
- Creating a new Database-Entry ( where is omitted )
777
-
778
- otherwise updating the Database-Entry (if present)
779
-
780
- The optional Block should provide a hash with attributes(properties). These are used if
781
- a new dataset is created.
782
- =end
783
- def create_or_update_document o_class , **args, &b
784
- logger.progname = 'Rest#CreateOrUpdateDocument'
785
- r= update_or_create_documents o_class, **args, &b
786
- if r.size > 1
787
- logger.error { "multible documents updated by #{ generate_sql_list( where )}" }
788
- end
789
- r.first # return_value
790
- end
791
- =begin
792
- Based on the query specified in :where records are updated according to :set
793
-
794
- Returns an Array of updated documents
795
-
796
- The optional Block should provide a hash with attributes(properties). These are used if
797
- a new dataset is created.
798
-
799
- ### das ist noch nicht rund.
800
- #
801
- =end
802
- def update_or_create_documents o_class , set: {}, where: {} , **args , &b
803
- logger.progname = 'Rest#UpdateOrCreateDocuments'
804
- if where.blank?
805
- [ create_document( o_class, attributes: set ) ]
806
- else
807
- set.extract!( where.keys ) # removes any keys from where in set
808
- possible_documents = get_documents from: class_name( o_class ), where: where, **args
809
- if possible_documents.empty?
810
- if block_given?
811
- more_where = yield # do Preparations prior to the creation of the dataset
812
- # if the block returns a Hash , it is merged into the insert_query.
813
- where.merge! more_where if more_where.is_a?(Hash)
814
- end
815
- [ create_document( o_class, attributes: set.merge(where) ) ]
816
- else
817
- possible_documents.map{| doc | doc.update( set: set ) }
818
- end
819
- end
820
- end
821
- =begin
822
- Deletes documents.
823
- They are defined by a query. All records which match the attributes are deleted.
824
- An Array with freed index-values is returned
825
- =end
826
- def delete_documents o_class, where: {}
827
- get_documents( from: o_class, where: where).map do |doc|
828
- if doc['@type']=='d' # document
829
- index = doc['@rid'][1,doc['@rid'].size] # omit the first character ('#')
830
- r=@res[ document_uri{ index }].delete
831
- index if r.code==204 && r.body.empty? # return_value
832
- end
833
-
834
- end
835
-
836
- end
837
- =begin
838
- Retrieves a Document from the Database as ActiveOrient::Model::{class}
839
- The argument can either be a rid (#[x}:{y}) or a link({x}:{y})
840
- If no Document is found, nil is returned
841
-
842
- In the optional block, a subset of properties can be defined (as array of names)
843
- =end
844
- def get_document rid
845
-
846
- rid = rid[1 .. rid.length] if rid[0]=='#'
847
-
848
- response = @res[ document_uri { rid } ].get
849
-
850
- raw_data = JSON.parse( response.body) #.merge( "#no_links" => "#no_links" )
851
- ActiveOrient::Model.orientdb_class( name: raw_data['@class']).new raw_data
852
-
853
- rescue RestClient::InternalServerError => e
854
- if e.http_body.split(':').last =~ /was not found|does not exist in database/
855
- nil
856
- else
857
- logger.progname 'OrientDB#GetDocument'
858
- logger.error "something went wrong"
859
- logger.error e.http_body.inspect
860
- raise
861
- end
862
- end
863
- =begin
864
- Lazy Updating of the given Document.
865
- =end
866
- def patch_document rid
867
- logger.progname = 'Rest#PatchDocument'
868
- content = yield
869
- if content.is_a? Hash
870
- content.each do | key, value |
871
- # puts "content: #{key}, #{value.class}"
872
- # content[key]= value.to_orient #if value.is_a? ActiveOrient::Model
873
- end
874
- @res[ document_uri { rid } ].patch content.to_orient.to_json
875
- else
876
- logger.error { "FAILED: The Block must provide an Hash with properties to be updated"}
877
- end
878
- end
879
- =begin
880
- update_documents classname,
881
- set: { :symbol => 'TWR' },
882
- where: { con_id: 340 }
883
-
884
- replaces the symbol to TWR in each record where the con_id is 340
885
- Both set and where take multible attributes
886
- returns the JSON-Response.
887
-
888
- =end
889
-
890
-
891
- def update_documents o_class, set: , where: {}
892
- url = "update #{class_name(o_class)} set "<< generate_sql_list(set) << compose_where(where)
893
- response = @res[ URI.encode( command_sql_uri << url) ].post '' #url.to_json
894
- end
895
-
896
- ## -----------------------------------------------------------------------------------------
897
- ##
898
- ## Functions and Batch
899
- ##
900
- ##
901
- ## -----------------------------------------------------------------------------------------
902
-
903
- =begin
904
- Execute a predefined Function
905
- =end
906
- def call_function *args
907
- #puts "uri:#{function_uri { args.join('/') } }"
908
- @res[ function_uri { args.join('/') } ].post ''
909
- rescue RestClient::InternalServerError => e
910
- puts JSON.parse(e.http_body)
911
- end
912
-
913
-
914
- =begin
915
- Executes a list of commands and returns the result-array (if present)
916
-
917
- structure of the provided block:
918
- [{ type: "cmd",
919
- language: "sql",
920
- command: "create class Person extends V"
921
- },
922
- (...)
923
- ]
924
-
925
- It was first used by ActiveOrient::Query.execute_queries
926
- Later I discovered that some Queries are not interpretated correctly by #GetDocuments
927
- but are submitted without Error via batch-processing.
928
- For Instance, this valid query
929
- > 'select expand( first_list[5].second_list[9] ) from base where label = 9 ' <
930
- can only be submitted via batch
931
-
932
- =end
933
- def execute classname = 'Myquery', transaction: true
934
- #puts "classname##execute:: #{classname}"
935
- batch = { transaction: transaction, operations: yield }
936
- unless batch[:operations].blank?
937
- # puts "post: #{batch.to_json}"
938
- response = @res[ batch_uri ].post batch.to_json
939
- # puts "response: #{JSON.parse(response.body)['result'].inspect}"
940
- if response.code == 200
941
- if response.body['result'].present?
942
- result= JSON.parse(response.body)['result']
943
- result.map do |x|
944
- if x.is_a? Hash
945
- if x.has_key?('@class')
946
- ActiveOrient::Model.orientdb_class( name: x['@class']).new x
947
- elsif x.has_key?( 'value' )
948
- x['value']
949
- else
950
- # puts "ActiveOrient::Execute"
951
- # puts "o_class: #{o_class.inspect}"
952
- ActiveOrient::Model.orientdb_class( name: classname).new x
953
- end
954
- end
955
- end.compact # return_value
956
-
957
- else
958
- response.body
959
- end
960
- else
961
- nil
962
- end
963
- end
964
- rescue RestClient::InternalServerError => e
965
- raise
966
-
967
- end
968
- =begin
969
- Converts a given name to the camelized database-classname
970
-
971
- Converts a given class-constant to the corresponding database-classname
972
-
973
- returns a valid database-class name, nil if the class not exists
974
- =end
975
- def class_name name_or_class
976
- name= if name_or_class.is_a? Class
977
- name_or_class.to_s.split('::').last
978
- elsif name_or_class.is_a? ActiveOrient::Model
979
- name_or_class.classname
980
- else
981
- name_or_class.to_s.camelize
982
- end
983
- if database_classes.include?(name)
984
- name
985
- elsif database_classes.include?(name.underscore)
986
- name.underscore
987
- else
988
- logger.progname = 'OrientDB#ClassName'
989
- logger.info{ "Classname #{name} not present in active Database" }
990
- nil
991
- end
992
- end
993
- # def compose_where arg
994
- # if arg.blank?
995
- # ""
996
- # elsif arg.is_a? String
997
- # if arg =~ /[w|W]here/
998
- # arg
999
- # else
1000
- # "where "+arg
1001
- # end
1002
- # elsif arg.is_a? Hash
1003
- # " where " + generate_sql_list(arg)
1004
- # end
1005
- # end
1006
- private
1007
-
1008
- #def generate_sql_list attributes={}
1009
- # attributes.map do | key, value |
1010
- # case value
1011
- # when Numeric
1012
- # key.to_s << " = " << value.to_s
1013
- # else # String, Symbol
1014
- # key.to_s << ' = ' << "\'#{ value }\'"
1015
- # # else
1016
- # # puts "ERROR, value-> #{value}, class -> #{value.class}"
1017
- # end
1018
- # end.join( ' and ' )
1019
- #
1020
- #end
1021
- #
1022
- def property_uri(this_class_name)
1023
- if block_given?
1024
- "property/#{ @database }/#{this_class_name}/" << yield
1025
- else
1026
- "property/#{ @database }/#{this_class_name}"
1027
- end
1028
- end
1029
-
1030
- def self.simple_uri *names
1031
- names.each do | name |
1032
- m_name = (name.to_s << '_uri').to_sym
1033
- define_method( m_name ) do | &b |
1034
- if b
1035
- "#{name.to_s}/"<< @database << "/" << b.call
1036
- else
1037
- "#{name.to_s}/"<< @database
1038
- end # branch
1039
- end # def
1040
- end
1041
- end
1042
-
1043
- def self.sql_uri *names
1044
- names.each do | name |
1045
- define_method ((name.to_s << '_sql_uri').to_sym) do
1046
- "#{name.to_s}/" << @database << "/sql/"
1047
- end
1048
- end
1049
- end
1050
-
1051
- simple_uri :database, :document, :class, :batch, :function
1052
- sql_uri :command , :query
1053
-
1054
-
1055
-
1056
- end # class
1057
- end # module
1058
- __END__
1059
-