active-orient 0.42 → 0.79

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 (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
@@ -0,0 +1,147 @@
1
+ module RestChange
2
+
3
+ ############### DATABASE ####################
4
+
5
+ # Changes the working-database to {name}
6
+
7
+ def change_database name
8
+ @classes = []
9
+ @database = name
10
+ ActiveOrient.database = name
11
+ end
12
+
13
+ ############# OBJECTS #################
14
+
15
+ =begin
16
+ Convient update of the dataset by calling sql-patch
17
+
18
+ The argument record can be specified as ActiveOrient::Model-instance or as rid-string( #0:0 )
19
+
20
+ called from ModelRecord#update
21
+
22
+ if the update was successful, the updated data are returned as Hash.
23
+
24
+ First the cached object is retrieved
25
+ Its modified by the parameters provided
26
+ and then patched
27
+
28
+ better would be: update the cached object and patch that.
29
+ =end
30
+
31
+ def update record, attributes , version=0
32
+ r = ActiveOrient::Model.autoload_object record.rid
33
+ return(false) unless r.is_a?(ActiveOrient::Model)
34
+ version = r.version if version.zero?
35
+ result = patch_record(r.rid) do
36
+ attributes.merge({'@version' => version, '@class' => r.class.ref_name })
37
+ end
38
+ # returns a new instance of ActiveOrient::Model and updates any reference on rid
39
+ # It's assumed, that the version
40
+ # if the patch is not successfull no string is returned and thus no record is fetched
41
+ # JSON.parse(result) if result.is_a?(String)
42
+ if result.is_a?(String)
43
+ JSON.parse(result) # return value
44
+ else
45
+ logger.error{ "REST::Update was not successfull" }
46
+ nil # returnvalue
47
+ end
48
+ end
49
+
50
+
51
+ =begin
52
+ Example:
53
+ ORD.update_documents classname, set: {:symbol => 'TWR'}, where: {con_id: 340}
54
+
55
+ Replaces the symbol to TWR in each record where the con_id is 340
56
+
57
+ Both set and where take multiple attributes
58
+
59
+ Returns the JSON-Response.
60
+
61
+ # todo: clear the rid-cache
62
+ =end
63
+
64
+ def update_records o_class, set:{}, where: {}, remove: nil
65
+ logger.progname = 'RestChange#UpdateRecords'
66
+ count = execute do
67
+ if set.present?
68
+ "UPDATE #{classname(o_class)} SET #{generate_sql_list(set)} #{compose_where(where)}"
69
+ elsif remove.present?
70
+ "UPDATE #{classname(o_class)} remove #{remove} #{compose_where(where)}"
71
+ end
72
+ end &.first
73
+ rescue Exception => e
74
+ logger.error{e.message}
75
+ nil
76
+
77
+
78
+ end
79
+
80
+ # Lazy Updating of the given Record.
81
+ # internal using while updating records
82
+ def patch_record rid # :nodoc: (used by #update )
83
+ logger.progname = 'RestChange#PatchRecord'
84
+ content = yield
85
+ if content.is_a? Hash
86
+ begin
87
+ @res["/document/#{ActiveOrient.database}/#{rid}"].patch content.to_orient.to_json
88
+ rescue RestClient::InternalServerError => e
89
+ sentence= JSON.parse( e.response)['errors'].last['content']
90
+ logger.error{sentence}
91
+ logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
92
+ logger.error{e.message.to_s}
93
+ end
94
+ else
95
+ logger.error{"PATCH FAILED: The Block must provide an Hash with properties to be updated"}
96
+ end
97
+ end
98
+ alias patch_document patch_record
99
+
100
+
101
+ #### EXPERIMENTAL ##########
102
+
103
+ =begin
104
+ Used to add restriction or other properties to the Property of a Class.
105
+ See http://orientdb.com/docs/2.1/SQL-Alter-Property.html
106
+ =end
107
+
108
+ def alter_property o_class, property:, attribute: "DEFAULT", alteration: # :nodoc: because untested
109
+ logger.progname = 'RestChange#AlterProperty'
110
+ begin
111
+ attribute.to_s! unless attribute.is_a? String
112
+ attribute.capitalize_first_letter
113
+ case attribute
114
+ when "LINKEDCLASS", "LINKEDTYPE", "NAME", "REGEX", "TYPE", "REGEX", "COLLATE", "CUSTOM"
115
+ unless alteration.is_a? String
116
+ logger.error{"#{alteration} should be a String."}
117
+ return 0
118
+ end
119
+ when "MIN", "MAX"
120
+ unless alteration.is_a? Integer
121
+ logger.error{"#{alteration} should be an Integer."}
122
+ return 0
123
+ end
124
+ when "MANDATORY", "NOTNULL", "READONLY"
125
+ unless alteration.is_a? TrueClass or alteration.is_a? FalseClass
126
+ logger.error{"#{alteration} should be an Integer."}
127
+ return 0
128
+ end
129
+ when "DEFAULT"
130
+ else
131
+ logger.error{"Wrong attribute."}
132
+ return 0
133
+ end
134
+
135
+ name_class = classname(o_class)
136
+ execute name_class, transaction: false do # To execute commands
137
+ [{ type: "cmd",
138
+ language: 'sql',
139
+ command: "ALTER PROPERTY #{name_class}.#{property} #{attribute} #{alteration}"}]
140
+ end
141
+ rescue Exception => e
142
+ logger.error{e.message}
143
+ end
144
+ end
145
+
146
+
147
+ end
@@ -0,0 +1,279 @@
1
+ module RestCreate
2
+
3
+ ######### DATABASE ##########
4
+
5
+ =begin
6
+ Creates a database with the given name and switches to this database as working-database. Types are either 'plocal' or 'memory'
7
+
8
+ Returns the name of the working-database
9
+ =end
10
+
11
+ def create_database type: 'plocal', database:
12
+ logger.progname = 'RestCreate#CreateDatabase'
13
+ old_d = ActiveOrient.database
14
+ ActiveOrient.database_classes = {}
15
+ ActiveOrient.database = database
16
+ begin
17
+ response = @res["database/#{ActiveOrient.database}/#{type}"].post ""
18
+ if response.code == 200
19
+ logger.info{"Database #{ActiveOrient.database} successfully created and stored as working database"}
20
+ else
21
+ logger.error{"Database #{ActiveOrient.database} was NOT created. Working Database is still #{ActiveOrient.database}"}
22
+ ActiveOrient.database = old_d
23
+ end
24
+ rescue RestClient::InternalServerError => e
25
+ logger.error{"Database #{ActiveOrient.database} was NOT created. Working Database is still #{ActiveOrient.database}"}
26
+ ActiveOrient.database = old_d
27
+ end
28
+ ActiveOrient.database # return_value
29
+ end
30
+
31
+
32
+
33
+ =begin
34
+
35
+ This is the historic method CreateClasses. It's just performing the database-stuff and now private
36
+
37
+ it takes superclass and abstract (a hash) as block
38
+
39
+ usually, only one classname is provided, however the method takes a row of classnames as argument
40
+
41
+ #todo reintegrate the ability to create abstract classes
42
+ =end
43
+ private
44
+ def create_this_class *db_classname #nodoc#
45
+ if block_given?
46
+ additional_args = yield
47
+ superclass = additional_args[ :superclass ]
48
+ abstract = additional_args[ :abstract ].presence || nil
49
+ else
50
+ superclass = nil
51
+ abstract = nil
52
+ end
53
+ #
54
+ command= db_classname.map do | database_class |
55
+ c = if superclass.present?
56
+ "CREATE CLASS #{database_class} EXTENDS #{superclass}"
57
+ else
58
+ "CREATE CLASS #{database_class} "
59
+ end
60
+ c << " ABSTRACT" if abstract.present?
61
+ # { type: "cmd", language: 'sql', command: c } # return value 4 command
62
+ c
63
+ end
64
+ # execute anything as batch, don't roll back in case of an error
65
+
66
+ execute transaction: false, tolerated_error_code: /already exists/ do
67
+ command
68
+ end
69
+
70
+ rescue ArgumentError => e
71
+ logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
72
+ rescue ActiveOrient::Error::ServerError
73
+ # do nothing
74
+ end
75
+
76
+
77
+ public
78
+
79
+ ############## OBJECT #############
80
+
81
+
82
+
83
+ =begin
84
+ Creates a Record in the Database and returns this as ActiveOrient::Model-Instance
85
+
86
+ Creates a Record with the attributes provided in the attributes-hash e.g.
87
+ create_record @classname, attributes: {con_id: 343, symbol: 'EWTZ'}
88
+
89
+ Puts the database-response into the cache by default
90
+
91
+
92
+ =end
93
+
94
+ def create_record o_class, attributes: {}, cache: true # use Model#create instead
95
+ logger.progname = 'RestCreate#CreateRecord'
96
+ attributes = yield if attributes.empty? && block_given?
97
+ # @class must not quoted! Quote only attributes(strings)
98
+ post_argument = {'@class' => classname(o_class)}.merge(attributes.to_orient)
99
+ begin
100
+ response = @res["/document/#{ActiveOrient.database}"].post post_argument.to_json
101
+ data = JSON.parse(response.body)
102
+ the_object = ActiveOrient::Model.orientdb_class(name: data['@class']).new data ## return_value
103
+ if cache
104
+ ActiveOrient::Base.store_rid( the_object )
105
+ else
106
+ the_object
107
+ end
108
+ rescue RestClient::InternalServerError => e
109
+ sentence= JSON.parse( e.response)['errors'].last['content']
110
+ # puts sentence.to_s
111
+ if sentence =~ /found duplicated key/
112
+ rid = sentence.split("#").last
113
+ logger.info{ "found duplicated Key --> loaded #{rid} instead of creating "}
114
+ ## reading database content -- maybe update attributes?
115
+ get_record rid
116
+ else
117
+ response = JSON.parse(e.response)['errors'].pop
118
+ logger.error{response['content'].split(':')[1..-1].join(':')}
119
+ logger.error{"No Object allocated"}
120
+ nil # return_value
121
+ end
122
+ rescue Errno::EADDRNOTAVAIL => e
123
+ sleep(2)
124
+ retry
125
+ end
126
+ end
127
+ alias create_document create_record
128
+
129
+ # UPDATE <class>|CLUSTER:<cluster>|<recordID>
130
+ # [SET|INCREMENT|ADD|REMOVE|PUT <field-name> = <field-value>[,]*]|[CONTENT|MERGE <JSON>]
131
+ # [UPSERT]
132
+ # [RETURN <returning> [<returning-expression>]]
133
+ # [WHERE <conditions>]
134
+ # [LOCK default|record]
135
+ # [LIMIT <max-records>] [TIMEOUT <timeout>]
136
+
137
+ =begin
138
+ update or insert one record is implemented as upsert.
139
+ The where-condition is merged into the set-attributes if its a hash.
140
+ Otherwise it's taken unmodified.
141
+
142
+ The method returns the included or the updated dataset
143
+
144
+ ## to do
145
+ # yield works for updated and for inserted datasets
146
+ # upsert ( ) do | what, record |
147
+ # if what == :insert
148
+ # do stuff with insert
149
+ # if what == :update
150
+ # do stuff with update
151
+ # end
152
+ # returns nil if no insert and no update was made, ie. the dataset is identical to the given attributes
153
+ =end
154
+ def upsert o_class, set: , where: # use Model#Upsert instead
155
+ logger.progname = 'RestCreate#Upsert'
156
+ specify_return_value = "return after @rid"
157
+ # set.merge! where if where.is_a?( Hash ) # copy where attributes to set
158
+ command = "Update #{classname(o_class)} set #{generate_sql_list( set ){','}} upsert #{specify_return_value} #{compose_where where}"
159
+ result = execute( tolerated_error_code: /found duplicated key/){ command }
160
+ # puts "result #{result.inspect}"
161
+ result =result.pop if result.is_a? Array
162
+ end
163
+ ############### PROPERTIES #############
164
+
165
+ =begin
166
+ Creates properties
167
+
168
+ and (if defined in the provided block) associates an index
169
+ create_properties(classname or class, properties as hash){index}
170
+
171
+ The default-case
172
+ create_properties(:my_high_sophisticated_database_class,
173
+ con_id: {type: :integer},
174
+ details: {type: :link, linked_class: 'Contracts'}) do
175
+ contract_idx: :notunique
176
+ end
177
+
178
+ A composite index
179
+ create_properties(:my_high_sophisticated_database_class,
180
+ con_id: {type: :integer},
181
+ symbol: {type: :string}) do
182
+ {name: 'indexname',
183
+ on: [:con_id, :details] # default: all specified properties
184
+ type: :notunique # default: :unique
185
+ }
186
+ end
187
+ =end
188
+
189
+ def create_properties o_class, all_properties, &b
190
+ logger.progname = 'RestCreate#CreateProperties'
191
+ all_properties_in_a_hash = Hash.new #WithIndifferentAccess.new
192
+ all_properties.each{|field, args| all_properties_in_a_hash.merge! translate_property_hash(field, args)}
193
+ count=0
194
+ # puts "all_properties_in_a_hash #{all_properties_in_a_hash.to_json}"
195
+ begin
196
+ if all_properties_in_a_hash.is_a?(Hash)
197
+ response = @res["/property/#{ActiveOrient.database}/#{classname(o_class)}"].post all_properties_in_a_hash.to_json
198
+ # puts response.inspect
199
+ # response.body.to_i returns response.code, only to_f.to_i returns the correct value
200
+ count= response.body.to_f.to_i if response.code == 201
201
+ end
202
+ rescue RestClient::InternalServerError => e
203
+ logger.progname = 'RestCreate#CreateProperties'
204
+ response = JSON.parse(e.response)['errors'].pop
205
+ error_message = response['content'].split(':').last
206
+ logger.error{"Properties in #{classname(o_class)} were NOT created"}
207
+ logger.error{"The Error was: #{response['content'].split(':').last}"}
208
+ nil
209
+ end
210
+ ### index
211
+ if block_given?# && count == all_properties_in_a_hash.size
212
+ index = yield
213
+ if index.is_a?(Hash)
214
+ if index.size == 1
215
+ create_index o_class, name: index.keys.first, on: all_properties_in_a_hash.keys, type: index.values.first
216
+ else
217
+ index_hash = {type: :unique, on: all_properties_in_a_hash.keys}.merge index
218
+ create_index o_class, name: index_hash[:name], on: index_hash[:on], type: index_hash[:type]
219
+ end
220
+ end
221
+ end
222
+ count # return_value
223
+ end
224
+
225
+ =begin
226
+ Create a single property.
227
+
228
+ Supported types: https://orientdb.com/docs/last/SQL-Create-Property.html
229
+
230
+ If an index is to be specified, it's defined in the optional block
231
+
232
+ create_property(class, field){:unique | :notunique}
233
+ --> creates an automatic-Index on the given field
234
+
235
+ create_property(class, field){{»name« => :unique | :notunique | :full_text}}
236
+ --> creates a manual index
237
+ =end
238
+
239
+ def create_property o_class, field, index: nil, **args, &b
240
+ logger.progname = 'RestCreate#CreateProperty'
241
+ args= { type: :string} if args.blank? # the default case
242
+ c = create_properties o_class, {field => args}
243
+ if index.nil? && block_given?
244
+ index = yield
245
+ end
246
+ if index.present?
247
+ if index.is_a?(String) || index.is_a?(Symbol)
248
+ create_index o_class, name: field, type: index
249
+ elsif index.is_a? Hash
250
+ bez = index.keys.first
251
+ create_index o_class, name: bez, type: index[bez], on: [field]
252
+ end
253
+ end
254
+ end
255
+
256
+ ################# INDEX ###################
257
+
258
+ # Used to create an index
259
+
260
+ def create_index o_class, name:, on: :automatic, type: :unique
261
+ logger.progname = 'RestCreate#CreateIndex'
262
+ c = classname o_class
263
+ if execute( transaction: false, tolerated_error_code: /found duplicated key/) do
264
+ if on == :automatic
265
+ "CREATE INDEX #{c}.#{name} #{type.to_s.upcase}"
266
+ elsif on.is_a? Array
267
+ "CREATE INDEX #{name} ON #{c}(#{on.join(', ')}) #{type.to_s.upcase}"
268
+ else
269
+ "CREATE INDEX #{name} ON #{c}(#{on.to_s}) #{type.to_s.upcase}"
270
+ end
271
+ end
272
+
273
+ logger.info{"Index on #{c} based on #{name} created."}
274
+ else
275
+ logger.error {"index #{name}.#{type} on #{c} NOT created"}
276
+ end
277
+ end
278
+
279
+ end
@@ -0,0 +1,134 @@
1
+ module RestDelete
2
+
3
+ ######### DATABASE ##########
4
+
5
+ =begin
6
+ Deletes the database and returns true on success
7
+ After the removal of the database, the working-database might be empty
8
+ =end
9
+
10
+ def delete_database database:
11
+ logger.progname = 'RestDelete#DeleteDatabase'
12
+ old_ds = ActiveOrient.database
13
+ change_database database
14
+ begin
15
+ response = @res["/database/#{ActiveOrient.database}"].delete
16
+ if database == old_ds
17
+ change_database 'temp'
18
+ logger.info{"Working database deleted, switched to temp"}
19
+ else
20
+ change_database old_ds
21
+ logger.info{"Database #{database} deleted, working database is still #{ActiveOrient.database}"}
22
+ end
23
+ rescue RestClient::InternalServerError => e
24
+ change_database old_ds
25
+ logger.info{"Database #{database} NOT deleted, working database is still #{ActiveOrient.database}"}
26
+ end
27
+ !response.nil? && response.code == 204 ? true : false
28
+ end
29
+
30
+ ######### CLASS ##########
31
+
32
+ =begin
33
+ Deletes the specified class and returns true on success
34
+ todo: remove all instances of the class
35
+ =end
36
+
37
+ def delete_class o_class
38
+ cl = classname(o_class)
39
+ return if cl.nil?
40
+ logger.progname = 'RestDelete#DeleteClass'
41
+
42
+ begin
43
+ ## to do: if cl contains special characters, enclose with backticks
44
+ response = @res["/class/#{ActiveOrient.database}/#{cl}"].delete
45
+ if response.code == 204
46
+ logger.info{"Class #{cl} deleted."}
47
+ ActiveOrient.database_classes.delete(cl)
48
+ end
49
+ rescue RestClient::InternalServerError => e
50
+ sentence= JSON.parse( e.response)['errors'].last['content']
51
+ if ActiveOrient.database_classes.has_key? cl
52
+ logger.error{"Class #{cl} still present."}
53
+ logger.error{ sentence }
54
+ false
55
+ else
56
+ logger.error{e.inspect}
57
+ true
58
+ end
59
+ rescue Exception => e
60
+ logger.error{e.message}
61
+ logger.error{e.inspect}
62
+ end
63
+ end
64
+
65
+ ############## RECORD #############
66
+
67
+ =begin
68
+ Deletes a single Record when providing a single rid-link (#00:00) or a record
69
+ Deletes multible Records when providing a list of rid-links or a record
70
+ Todo: implement delete_edges after querying the database in one statement
71
+
72
+ Example:
73
+ record = Vertex.create_document attributes: { something: 'something' }
74
+ Vertex.delete_record record
75
+
76
+ records= (1..100).map{|x| Vertex.create_document attributes: { something: x } }
77
+ Vertex.delete_record *records
78
+
79
+ delete_records provides the removal of datasets after quering the database.
80
+ =end
81
+
82
+ def delete_record *rid
83
+ logger.progname = "ActiveOrient::RestDelete#DeleteRecord"
84
+ ridvec= rid.map( &:to_orient).flatten
85
+ unless ridvec.empty?
86
+ ridvec.map do |rid|
87
+ begin
88
+ ActiveOrient::Base.remove_rid( ActiveOrient::Base.get_rid(rid) )
89
+ @res["/document/#{ActiveOrient.database}/#{rid[1..-1]}"].delete
90
+ rescue RestClient::InternalServerError => e
91
+ logger.error{"Record #{rid} NOT deleted"}
92
+ rescue RestClient::ResourceNotFound
93
+ logger.error{"Record #{rid} does not exist in the database"}
94
+ else
95
+ logger.info{"Record #{rid} deleted"}
96
+ end
97
+ end
98
+ else
99
+ logger.info{"No record deleted."}
100
+ return nil
101
+ end
102
+ end
103
+ alias delete_document delete_record
104
+
105
+ =begin
106
+ Deletes records. They are defined by a query. All records which match the attributes are deleted.
107
+ An Array with freed index-values is returned
108
+ =end
109
+
110
+ def delete_records o_class, where: {}
111
+ logger.progname = 'RestDelete#DeleteRecords'
112
+ records_to_delete = get_records(from: o_class, where: where)
113
+ if records_to_delete.empty?
114
+ logger.info{"No record found"}
115
+ else
116
+ delete_record records_to_delete
117
+ end
118
+ end
119
+ alias delete_documents delete_records
120
+
121
+ ################ PROPERTY #############
122
+
123
+ def delete_property o_class, field
124
+ logger.progname = 'RestDelete#DeleteProperty'
125
+ begin
126
+ response = @res["/property/#{ActiveOrient.database}/#{classname(o_class)}/#{field}"].delete
127
+ true if response.code == 204
128
+ rescue RestClient::InternalServerError => e
129
+ logger.error{"Property #{field} in class #{classname(o_class)} NOT deleted" }
130
+ false
131
+ end
132
+ end
133
+
134
+ end