active-orient 0.4 → 0.80

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