active-orient 0.42 → 0.79

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/Gemfile +13 -5
  4. data/Guardfile +12 -4
  5. data/README.md +67 -280
  6. data/VERSION +1 -1
  7. data/active-orient.gemspec +6 -5
  8. data/bin/active-orient-0.6.gem +0 -0
  9. data/bin/active-orient-console +85 -0
  10. data/config/boot.rb +72 -1
  11. data/config/config.yml +10 -0
  12. data/config/connect.yml +9 -4
  13. data/examples/books.rb +92 -40
  14. data/examples/streets.rb +89 -85
  15. data/examples/test_commands.rb +97 -0
  16. data/examples/test_commands_2.rb +59 -0
  17. data/examples/test_commands_3.rb +55 -0
  18. data/examples/test_commands_4.rb +33 -0
  19. data/examples/time_graph.md +162 -0
  20. data/lib/active-orient.rb +75 -9
  21. data/lib/base.rb +238 -169
  22. data/lib/base_properties.rb +68 -60
  23. data/lib/class_utils.rb +226 -0
  24. data/lib/database_utils.rb +98 -0
  25. data/lib/init.rb +79 -0
  26. data/lib/java-api.rb +442 -0
  27. data/lib/jdbc.rb +211 -0
  28. data/lib/model/custom.rb +26 -0
  29. data/lib/model/edge.rb +70 -0
  30. data/lib/model/model.rb +134 -0
  31. data/lib/model/the_class.rb +607 -0
  32. data/lib/model/the_record.rb +266 -0
  33. data/lib/model/vertex.rb +236 -0
  34. data/lib/orientdb_private.rb +48 -0
  35. data/lib/other.rb +371 -0
  36. data/lib/railtie.rb +68 -0
  37. data/lib/rest/change.rb +147 -0
  38. data/lib/rest/create.rb +279 -0
  39. data/lib/rest/delete.rb +134 -0
  40. data/lib/rest/operations.rb +211 -0
  41. data/lib/rest/read.rb +171 -0
  42. data/lib/rest/rest.rb +112 -0
  43. data/lib/rest_disabled.rb +24 -0
  44. data/lib/support/logging.rb +38 -0
  45. data/lib/support/orient.rb +196 -0
  46. data/lib/support/orientquery.rb +469 -0
  47. data/rails.md +154 -0
  48. data/rails/activeorient.rb +32 -0
  49. data/rails/config.yml +10 -0
  50. data/rails/connect.yml +17 -0
  51. metadata +65 -24
  52. data/active-orient-0.4.gem +0 -0
  53. data/active-orient-0.41.gem +0 -0
  54. data/lib/model.rb +0 -468
  55. data/lib/orient.rb +0 -98
  56. data/lib/query.rb +0 -88
  57. data/lib/rest.rb +0 -1059
  58. data/lib/support.rb +0 -372
  59. data/test.rb +0 -4
  60. data/usecase.md +0 -91
@@ -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