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,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