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,211 @@
1
+ module RestOperations
2
+
3
+ # Execute a predefined Function
4
+
5
+ # untested
6
+ def call_function *args
7
+ # puts "uri:#{function_uri { args.join('/') } }"
8
+ begin
9
+ term = args.join('/')
10
+ @res["/function/#{@database}/#{term}"].post ''
11
+ rescue RestClient::InternalServerError => e
12
+ puts JSON.parse(e.http_body)
13
+ end
14
+ end
15
+
16
+ # Used to count the Records in relation of the arguments
17
+ #
18
+ # Overwritten by Model#Count
19
+ def count **args
20
+ logger.progname = 'RestOperations#CountRecords'
21
+ query = OrientSupport::OrientQuery.new args
22
+ query.projection << 'COUNT(*)'
23
+ result = get_records raw: true, query: query
24
+ result.first["COUNT(*)"] rescue 0 # return_value
25
+ end
26
+ =begin
27
+ --
28
+ ## historic method
29
+ # def manipulate_relation record, method, array, items # :nodoc: #
30
+ # execute_array = Array.new
31
+ # method = method.to_s.upcase
32
+ #
33
+ # add_2_execute_array = -> (it) do
34
+ # command = "UPDATE ##{record.rid} #{method} #{array} = #{it.to_or } " #updating}"
35
+ # command.gsub!(/\"/,"") if it.is_a? Array
36
+ # puts "COMMAND:: #{command}"
37
+ # execute_array << {type: "cmd", language: "sql", command: command}
38
+ # end
39
+ #
40
+ # items.to_a.each{|x| add_2_execute_array[x] }
41
+ ## puts "******************"
42
+ ## puts record.inspect
43
+ ## puts "-----"
44
+ ## puts execute_array.join('\n')
45
+ # r= execute{ execute_array }
46
+ # puts record.inspect
47
+ # puts r.inspect
48
+ ## puts "******************"
49
+ # if r.present?
50
+ # case method
51
+ # when 'ADD'
52
+ # items.each{|x| record.attributes[array] << x}
53
+ # when 'REMOVE'
54
+ # items.map{|x| record.attributes[array].delete x}
55
+ # else
56
+ # end
57
+ # record.increment_version
58
+ # end
59
+ # end
60
+ ++
61
+ =end
62
+
63
+
64
+ =begin
65
+ Executes a list of commands and returns the result-array (if present)
66
+
67
+ (External use)
68
+
69
+ If soley a string is provided in the block, a minimal database-console is realized.
70
+ i.e.
71
+
72
+ ORD.execute{ 'select from #25:0' }
73
+
74
+ (Internal Use)
75
+
76
+ Structure of the provided block:
77
+ [{type: "cmd", language: "sql", command: "create class Person extends V"}, (...)]
78
+ --
79
+ It was first used by ActiveOrient::Query.execute_queries
80
+ Later I (topofocus) discovered that some Queries are not interpretated correctly by #GetRecords but are submitted without Error via batch-processing.
81
+ For instance, this valid query
82
+ select expand(first_list[5].second_list[9]) from base where label = 9
83
+ can only be submitted via batch
84
+ ++
85
+ Parameters:
86
+
87
+ transaction: true|false Perform the batch as transaction
88
+ tolerate_error_code: /a regular expression/
89
+ Statements to execute are provided via block
90
+ These statements are translated to json and transmitted to the database. Example:
91
+
92
+ { type: "cmd",
93
+ language: 'sql',
94
+ command: "CREATE EDGE #{classname(o_class)} FROM #{from.to_orient} TO #{to.to_orient}"}
95
+
96
+ Multible statements are transmitted at once if the Block provides an Array of statements.
97
+
98
+
99
+ =end
100
+
101
+ def read_transaction
102
+ @transaction
103
+ end
104
+ def execute transaction: false, tolerated_error_code: nil, process_error: true, raw: nil
105
+ @transaction = [] unless @transaction.is_a?(Array)
106
+ if block_given?
107
+ command = yield
108
+ command.is_a?(Array) ? command.each{|c| @transaction << c} : @transaction << command
109
+ else
110
+ logger.error { "No Block provided to execute" }
111
+ return nil
112
+ end
113
+
114
+ # puts "transaction #{@transaction.inspect}"
115
+ unless transaction == :prepare
116
+ commands = @transaction.map{|y| y if y.is_a? String }.compact
117
+ @transaction.delete_if{|y| y if y.is_a?(String)}
118
+ #puts "tn #{commands.inspect}"
119
+ return nil if commands.empty?
120
+ if commands.size >1
121
+ @transaction << { type: 'script', language: 'sql', script: commands }
122
+ elsif transaction == false
123
+ @transaction = commands.first
124
+ else
125
+ transaction = true
126
+ @transaction << { type: 'cmd', language: 'sql', command: commands.first }
127
+ end
128
+ _execute transaction, tolerated_error_code, process_error, raw
129
+ end
130
+ end
131
+
132
+
133
+ def _execute transaction, tolerated_error_code, process_error, raw
134
+
135
+ logger.progname= "Execute"
136
+ begin
137
+ response = if @transaction.is_a?(Array)
138
+ @transaction.compact!
139
+ return nil if @transaction.empty?
140
+ # transaction is true only for multible statements
141
+ # batch[:transaction] = transaction & batch[:operations].size >1
142
+ logger.info{ @transaction.map{|y|y[:command]}.join(";\n ") }
143
+ logger.info{ @transaction.map{|y|y[:script]}.join(";\n ") }
144
+ batch= { transaction: transaction, operations: @transaction }
145
+ puts "batch: #{batch.inspect}"
146
+ @res["/batch/#{ActiveOrient.database}"].post batch.to_json
147
+ else
148
+ logger.info{ @transaction }
149
+ @res["/command/#{ActiveOrient.database}/sql"].post @transaction #.to_json
150
+ end
151
+ rescue RestClient::BadRequest => f
152
+ # extract the misspelled query in logfile and abort
153
+ sentence= JSON.parse( f.response)['errors'].last['content']
154
+ logger.fatal{ " BadRequest --> #{sentence.split("\n")[1]} " }
155
+ puts "Query not recognized"
156
+ puts sentence
157
+ raise
158
+ rescue RestClient::InternalServerError => e
159
+ @transaction = []
160
+ sentence= JSON.parse( e.response)['errors'].last['content']
161
+ if tolerated_error_code.present? && e.response =~ tolerated_error_code
162
+ logger.debug('RestOperations#Execute'){ "tolerated_error::#{e.message}"}
163
+ logger.debug('RestOperations#Execute'){ e.message }
164
+ nil # return value
165
+ else
166
+ if process_error
167
+ # puts batch.to_json
168
+ # logger.error{e.response}
169
+ logger.error{sentence}
170
+ #logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
171
+ # logger.error{e.message.to_s}
172
+ else
173
+ raise
174
+ end
175
+ end
176
+ rescue Errno::EADDRNOTAVAIL => e
177
+ sleep(2)
178
+ retry
179
+ else # code to execute if no exception is raised
180
+ @transaction = []
181
+ if response.code == 200
182
+ if response.body['result'].present?
183
+ result=JSON.parse(response.body)['result']
184
+ return result if raw.present?
185
+ result.map do |x|
186
+ if x.is_a? Hash
187
+ y = x.transform_keys{|y| y.delete('@').split('=').first.underscore.to_sym}
188
+ if y[:type] == 'd' #0.present? # == 'd' # x.has_key?("@type") &&
189
+ if y.has_key?(:class)
190
+ the_object = ActiveOrient::Model.orientdb_class( name: y[:class] ).new x
191
+ ActiveOrient::Base.store_rid( the_object ) # update cache
192
+ else # create a dummy class and fill with attributes from result-set
193
+ ActiveOrient::Model.orientdb_class(name: 'query' ).new x
194
+ end
195
+ else
196
+ # return the result or the corresponding dataset
197
+ r= y.map{ | _,v | v.is_a?(String) && v.rid? ? ActiveOrient::Model.get( v ): v }
198
+ y.size ==1 ? r.first : r # return raw instead of array if only one value is present
199
+ end
200
+ end
201
+ end.compact # return_value
202
+ else
203
+ response.body
204
+ end
205
+ else
206
+ nil
207
+ end
208
+ end
209
+ end
210
+
211
+ end
@@ -0,0 +1,171 @@
1
+ module RestRead
2
+
3
+ ############# DATABASE #############
4
+
5
+ # Returns an Array with available Database-Names as Elements
6
+ #
7
+ # ORD.get_databases
8
+ # => ["temp", "GratefulDeadConcerts", (...)]
9
+ def get_databases
10
+ JSON.parse(@res["/listDatabases"].get.body)['databases']
11
+ end
12
+
13
+ =begin
14
+ Returns an Array with (unmodified) Class-attribute-hash-Elements
15
+
16
+ »get_classes 'name', 'superClass'« returns
17
+ [ {"name"=>"E", "superClass"=>""},
18
+ {"name"=>"OFunction", "superClass"=>""},
19
+ {"name"=>"ORole", "superClass"=>"OIdentity"}
20
+ (...) ]
21
+ =end
22
+ def get_classes *attributes
23
+ begin
24
+ response = @res["/database/#{ActiveOrient.database}"].get
25
+ if response.code == 200
26
+ classes = JSON.parse(response.body)['classes']
27
+ unless attributes.empty?
28
+ classes.map{|y| y.select{|v,_| attributes.include?(v)}}
29
+ else
30
+ classes
31
+ end
32
+ else
33
+ []
34
+ end
35
+ rescue Exception => e
36
+ logger.progname = 'RestRead#GetClasses'
37
+ logger.error{e.message}
38
+ end
39
+ end
40
+
41
+
42
+ ############### CLASS ################
43
+
44
+ # Returns a JSON of the property of a class
45
+ #
46
+ # ORD.create_vertex_class a:
47
+ # ORD.get_class_properties A
48
+ # => {"name"=>"a", "superClass"=>"V", "superClasses"=>["V"], "alias"=>nil, "abstract"=>false, "strictmode"=>false, "clusters"=>[65, 66, 67, 68], "defaultCluster"=>65, "clusterSelection"=>"round-robin", "records"=>3}
49
+ #
50
+ def get_class_properties o_class
51
+ JSON.parse(@res["/class/#{ActiveOrient.database}/#{classname(o_class)}"].get)
52
+ rescue => e
53
+ logger.error e.message
54
+ nil
55
+ end
56
+
57
+
58
+ def print_class_properties o_class
59
+ puts "Detected Properties for class #{classname(o_class)}"
60
+ rp = get_class_properties o_class
61
+ n = rp['name']
62
+ if rp['properties'].nil?
63
+ puts "No property available"
64
+ else
65
+ puts rp['properties'].map{|x| "\t"+[n+'.'+x['name'], x['type'],x['linkedClass']].compact.join("\t-> ")}.join("\n")
66
+ end
67
+ rescue NoMethodError
68
+ puts "Class #{o_class} not present in database"
69
+ end
70
+
71
+ ############## OBJECT #################
72
+
73
+ =begin
74
+ Retrieves a Record from the Database
75
+
76
+ The argument can either be a rid "#{x}:{y}" or a link "{x}:{y}".
77
+
78
+ (to be specific: it must provide the methods rid? and to_orient, the latter must return the rid: "#[a}:{b}".)
79
+
80
+ If no Record is found, nil is returned
81
+
82
+ The rid-cache is not used or updated
83
+ =end
84
+
85
+ def get_record rid
86
+ begin
87
+ logger.progname = 'RestRead#GetRecord'
88
+ if rid.rid?
89
+ response = @res["/document/#{ActiveOrient.database}/#{rid.to_orient[1..-1]}"].get
90
+ raw_data = JSON.parse(response.body)
91
+ # ActiveOrient::Model.use_or_allocate( raw_data['@rid'] ) do
92
+ the_object= ActiveOrient::Model.orientdb_class(name: raw_data['@class']).new raw_data
93
+ ActiveOrient::Base.store_rid( the_object ) # update cache
94
+ else
95
+ logger.error { "Wrong parameter #{rid.inspect}. " }
96
+ nil
97
+ end
98
+ rescue RestClient::InternalServerError => e
99
+ if e.http_body.split(':').last =~ /was not found|does not exist in database/
100
+ nil
101
+ else
102
+ logger.error { "Something went wrong" }
103
+ logger.error { e.http_body.inspect }
104
+ raise
105
+ end
106
+ rescue RestClient::ResourceNotFound => e
107
+ logger.error { "RID: #{rid} ---> No Record present " }
108
+ ActiveOrient::Model.remove_rid rid # remove rid from cache
109
+ nil
110
+ rescue Exception => e
111
+ logger.error { "Something went wrong" }
112
+ logger.error { "RID: #{rid} - #{e.message}" }
113
+ raise
114
+ end
115
+ end
116
+ alias get_document get_record
117
+
118
+ =begin
119
+ Retrieves Records from a query
120
+
121
+ If raw is specified, the JSON-Array is returned, e.g.
122
+ {"@type"=>"d", "@rid"=>"#15:1", "@version"=>1, "@class"=>"DocumebntKlasse10", "con_id"=>343, "symbol"=>"EWTZ"}
123
+
124
+ Otherwise ActiveModel-Instances are created and returned.
125
+ In this case cached data are used in favour and its not checked if the database contents have changed.
126
+ =end
127
+
128
+ def get_records raw: false, query: nil, **args
129
+ query = OrientSupport::OrientQuery.new(args) if query.nil?
130
+ begin
131
+ logger.progname = 'RestRead#GetRecords'
132
+ url = "/query/#{ActiveOrient.database}/sql/" + query.compose(destination: :rest) + "/#{query.get_limit}"
133
+ response = @res[URI.encode(url)].get
134
+ JSON.parse(response.body)['result'].map do |record|
135
+ if raw
136
+ record
137
+ # query returns an anonymus class: Use the provided Block or the Dummy-Model MyQuery
138
+ elsif record['@class'].blank?
139
+ block_given? ? yield.new(record) : ActiveOrient::Model.orientdb_class(name: 'query' ).new( record )
140
+ else
141
+ the_object = ActiveOrient::Model.orientdb_class(name: record['@class']).new record
142
+ ActiveOrient::Base.store_rid( the_object ) # update cache
143
+ # end
144
+ end
145
+ end
146
+ # returns an array of updated objects
147
+
148
+ rescue RestClient::BadRequest => e
149
+ #puts e.inspect
150
+ logger.error { "-"*30 }
151
+ logger.error { "REST_READ#GET_RECORDS.URL ---> Wrong Query" }
152
+ logger.error { query.compose( destination: :rest).to_s }
153
+ logger.error { "Fired Statement: #{url.to_s} " }
154
+ response=""
155
+ rescue RestClient::InternalServerError => e
156
+ response = JSON.parse(e.response)['errors'].pop
157
+ logger.error{ "Interbak Server ERROR" }
158
+ logger.error{response['content'].split(':').last}
159
+ rescue URI::InvalidURIError => e
160
+ logger.error{"Invalid URI detected"}
161
+ logger.error query.to_s
162
+ logger.info{"Trying batch processing"}
163
+ sql_cmd = -> (command){{type: "cmd", language: "sql", command: command}}
164
+ response = execute{[sql_cmd[query.to_s]]}
165
+ logger.info{"Success: to avoid this delay use ActiveOrient::Model#query_database instead"}
166
+ response
167
+ end
168
+ end
169
+ alias get_documents get_records
170
+
171
+ end
@@ -0,0 +1,112 @@
1
+ require_relative "read.rb" # manage get
2
+ require_relative "create.rb" # manage create
3
+ require_relative "change.rb" # manage update
4
+ require_relative "operations.rb" # manage count, functions and execute
5
+ require_relative "delete.rb" # manage delete
6
+ require_relative "../support/logging"
7
+ require 'cgi'
8
+ require 'rest-client'
9
+
10
+ module ActiveOrient
11
+
12
+ =begin
13
+ OrientDB points to an OrientDB-Database.
14
+ The communication is based on the OrientDB-REST-API.
15
+
16
+ Its usually initialised through ActiveOrient::Init.connect
17
+
18
+ =end
19
+
20
+ class OrientDB
21
+ include OrientSupport::Support
22
+ include OrientSupport::Logging
23
+ include OrientDbPrivate
24
+ include DatabaseUtils
25
+ include ClassUtils
26
+ include RestRead
27
+ include RestCreate
28
+ include RestChange
29
+ include RestOperations
30
+ include RestDelete
31
+
32
+
33
+ #### INITIALIZATION ####
34
+
35
+ =begin
36
+ OrientDB is conventionally initialized.
37
+
38
+
39
+ The first call initialises database-name and -classes, server-adress and user-credentials.
40
+
41
+ Subsequent initialisations are made to initialise namespaced database classes, ie.
42
+
43
+ ORD = ActiveOrient.init.connect database: 'temp'
44
+ server: 'localhost',
45
+ port: 2480,
46
+ user: root,
47
+ password: root
48
+ module HC; end
49
+ ActiveOrient::Init.define_namespace { HC }
50
+ ActiveOrient::OrientDB.new preallocate: true
51
+
52
+
53
+
54
+
55
+
56
+ =end
57
+
58
+ def initialize database: nil, preallocate: true, model_dir: nil, **defaults
59
+ ActiveOrient.database ||= database || 'temp'
60
+ ActiveOrient.database_classes ||= Hash.new
61
+
62
+ ActiveOrient.default_server ||= { :server => defaults[:server] || 'localhost' ,
63
+ :port => defaults[:port] ||= 2480,
64
+ :user => defaults[:user].to_s ,
65
+ :password => defaults[:password].to_s }
66
+
67
+ @res = get_resource
68
+ connect()
69
+ database_classes # initialize @classes-array and ActiveOrient.database_classes
70
+ ActiveOrient::Base.logger = logger
71
+ ActiveOrient::Model.orientdb = self
72
+ ActiveOrient::Model.db = self
73
+ ActiveOrient::Model.keep_models_without_file ||= nil
74
+ preallocate_classes( model_dir ) if preallocate
75
+
76
+ end
77
+
78
+ def get_resource
79
+ login = [ActiveOrient.default_server[:user] , ActiveOrient.default_server[:password]]
80
+ server_adress = "http://#{ActiveOrient.default_server[:server]}:#{ActiveOrient.default_server[:port]}"
81
+ RestClient::Resource.new(server_adress, *login)
82
+ end
83
+
84
+ # Used to connect to the database
85
+
86
+ def connect
87
+ first_tentative = true
88
+ begin
89
+ database = ActiveOrient.database
90
+ logger.progname = 'OrientDB#Connect'
91
+ r = @res["/connect/#{database}"].get
92
+ if r.code == 204
93
+ logger.info{"Connected to database #{database}"}
94
+ true
95
+ else
96
+ logger.error{"Connection to database #{database} could NOT be established"}
97
+ nil
98
+ end
99
+ rescue RestClient::Unauthorized => e
100
+ if first_tentative
101
+ logger.info{"Database #{database} NOT present --> creating"}
102
+ first_tentative = false
103
+ create_database database: database
104
+ retry
105
+ else
106
+ Kernel.exit
107
+ end
108
+ end
109
+ end
110
+
111
+ end
112
+ end