active-orient 0.2

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