active-orient 0.4 → 0.5

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +8 -3
  4. data/Guardfile +12 -4
  5. data/README.md +221 -201
  6. data/VERSION +1 -1
  7. data/active-orient.gemspec +3 -2
  8. data/bin/active-orient-console +35 -0
  9. data/config/boot.rb +84 -16
  10. data/config/config.yml +10 -0
  11. data/config/connect.yml +6 -2
  12. data/create_project +19 -0
  13. data/examples/books.rb +86 -39
  14. data/examples/createTime.rb +91 -0
  15. data/examples/streets.rb +85 -84
  16. data/examples/test_commands.rb +92 -0
  17. data/examples/test_commands_2.rb +54 -0
  18. data/examples/test_commands_3.rb +48 -0
  19. data/examples/test_commands_4.rb +28 -0
  20. data/examples/time_graph/Gemfile +21 -0
  21. data/examples/time_graph/Guardfile +26 -0
  22. data/examples/time_graph/README.md +129 -0
  23. data/examples/time_graph/bin/active-orient-console +35 -0
  24. data/examples/time_graph/config/boot.rb +119 -0
  25. data/examples/time_graph/config/config.yml +8 -0
  26. data/examples/time_graph/config/connect.yml +17 -0
  27. data/examples/time_graph/config/init_db.rb +59 -0
  28. data/examples/time_graph/createTime.rb +51 -0
  29. data/examples/time_graph/lib/createTime.rb +82 -0
  30. data/examples/time_graph/model/day_of.rb +3 -0
  31. data/examples/time_graph/model/e.rb +6 -0
  32. data/examples/time_graph/model/edge.rb +53 -0
  33. data/examples/time_graph/model/monat.rb +19 -0
  34. data/examples/time_graph/model/stunde.rb +16 -0
  35. data/examples/time_graph/model/tag.rb +29 -0
  36. data/examples/time_graph/model/time_base.rb +6 -0
  37. data/examples/time_graph/model/time_of.rb +4 -0
  38. data/examples/time_graph/model/v.rb +3 -0
  39. data/examples/time_graph/model/vertex.rb +32 -0
  40. data/examples/time_graph/spec/lib/create_time_spec.rb +50 -0
  41. data/examples/time_graph/spec/rest_helper.rb +37 -0
  42. data/examples/time_graph/spec/spec_helper.rb +46 -0
  43. data/lib/active-orient.rb +56 -6
  44. data/lib/base.rb +149 -147
  45. data/lib/base_properties.rb +40 -41
  46. data/lib/class_utils.rb +301 -0
  47. data/lib/database_utils.rb +97 -0
  48. data/lib/init.rb +35 -0
  49. data/lib/java-api.rb +437 -0
  50. data/lib/jdbc.rb +211 -0
  51. data/lib/model/edge.rb +53 -0
  52. data/lib/model/model.rb +77 -0
  53. data/lib/model/the_class.rb +480 -0
  54. data/lib/model/the_record.rb +310 -0
  55. data/lib/model/vertex.rb +32 -0
  56. data/lib/orient.rb +113 -50
  57. data/lib/orientdb_private.rb +48 -0
  58. data/lib/other.rb +280 -0
  59. data/lib/query.rb +71 -73
  60. data/lib/rest/change.rb +124 -0
  61. data/lib/rest/create.rb +474 -0
  62. data/lib/rest/delete.rb +133 -0
  63. data/lib/rest/operations.rb +150 -0
  64. data/lib/rest/read.rb +150 -0
  65. data/lib/rest/rest.rb +111 -0
  66. data/lib/rest_disabled.rb +24 -0
  67. data/lib/support.rb +387 -296
  68. data/old_lib_functions/two_general_class.rb +139 -0
  69. data/usecase.md +49 -36
  70. data/usecase_oo.md +59 -0
  71. metadata +73 -9
  72. data/lib/model.rb +0 -461
  73. data/lib/rest.rb +0 -1036
  74. data/test.rb +0 -4
@@ -0,0 +1,133 @@
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
+ if get_database_classes(requery: true).include?(cl)
51
+ logger.error{"Class #{cl} still present."}
52
+ logger.error{e.inspect}
53
+ false
54
+ else
55
+ logger.error{e.inspect}
56
+ true
57
+ end
58
+ rescue Exception => e
59
+ logger.error{e.message}
60
+ logger.error{e.inspect}
61
+ end
62
+ end
63
+
64
+ ############## RECORD #############
65
+
66
+ =begin
67
+ Deletes a single Record when providing a single rid-link (#00:00) or a record
68
+ Deletes multible Records when providing a list of rid-links or a record
69
+ Todo: implement delete_edges after querying the database in one statement
70
+
71
+ Example:
72
+ record = Vertex.create_document attributes: { something: 'something' }
73
+ Vertex.delete_record record
74
+
75
+ records= (1..100).map{|x| Vertex.create_document attributes: { something: x } }
76
+ Vertex.delete_record *records
77
+
78
+ delete_records provides the removal of datasets after quering the database.
79
+ =end
80
+
81
+ def delete_record *rid
82
+ logger.progname = "ActiveOrient::RestDelete#DeleteRecord"
83
+ ridvec= rid.map( &:to_orient).flatten
84
+ unless ridvec.empty?
85
+ ridvec.map do |rid|
86
+ begin
87
+ ActiveOrient::Base.remove_rid( ActiveOrient::Base.get_rid(rid) )
88
+ @res["/document/#{ActiveOrient.database}/#{rid[1..-1]}"].delete
89
+ rescue RestClient::InternalServerError => e
90
+ logger.error{"Record #{rid} NOT deleted"}
91
+ rescue RestClient::ResourceNotFound
92
+ logger.error{"Record #{rid} does not exist in the database"}
93
+ else
94
+ logger.info{"Record #{rid} deleted"}
95
+ end
96
+ end
97
+ else
98
+ logger.info{"No record deleted."}
99
+ return nil
100
+ end
101
+ end
102
+ alias delete_document delete_record
103
+
104
+ =begin
105
+ Deletes records. They are defined by a query. All records which match the attributes are deleted.
106
+ An Array with freed index-values is returned
107
+ =end
108
+
109
+ def delete_records o_class, where: {}
110
+ logger.progname = 'RestDelete#DeleteRecords'
111
+ records_to_delete = get_records(from: o_class, where: where)
112
+ if records_to_delete.empty?
113
+ logger.info{"No record found"}
114
+ else
115
+ delete_record records_to_delete
116
+ end
117
+ end
118
+ alias delete_documents delete_records
119
+
120
+ ################ PROPERTY #############
121
+
122
+ def delete_property o_class, field
123
+ logger.progname = 'RestDelete#DeleteProperty'
124
+ begin
125
+ response = @res["/property/#{ActiveOrient.database}/#{classname(o_class)}/#{field}"].delete
126
+ true if response.code == 204
127
+ rescue RestClient::InternalServerError => e
128
+ logger.error{"Property #{field} in class #{classname(o_class)} NOT deleted" }
129
+ false
130
+ end
131
+ end
132
+
133
+ end
@@ -0,0 +1,150 @@
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
+
27
+
28
+ def manipulate_relation record, method, array, items # :nodoc: #
29
+ execute_array = Array.new
30
+ method = method.to_s.upcase
31
+
32
+ add_2_execute_array = -> (it) do
33
+ command = "UPDATE ##{record.rid} #{method} #{array} = #{it.to_orient } " #updating}"
34
+ command.gsub!(/\"/,"") if it.is_a? Array
35
+ #puts "COMMAND:: #{command}"
36
+ execute_array << {type: "cmd", language: "sql", command: command}
37
+ end
38
+
39
+ items.each{|x| add_2_execute_array[x] }
40
+ r= execute{ execute_array }
41
+
42
+ if r.present?
43
+ case method
44
+ when 'ADD'
45
+ items.each{|x| record.attributes[array] << x}
46
+ when 'REMOVE'
47
+ items.map{|x| record.attributes[array].delete x.is_a?(ActiveOrient::Model) ? x.rid : x}
48
+ else
49
+ end
50
+ record.increment_version
51
+ end
52
+ end
53
+ =begin
54
+ Executes a list of commands and returns the result-array (if present)
55
+
56
+ (External use)
57
+
58
+ If soley a string is provided in the block, a minimal database-console is realized.
59
+ i.e.
60
+
61
+ ORD.execute{ 'select from #25:0' }
62
+
63
+ (Internal Use)
64
+
65
+ Structure of the provided block:
66
+ [{type: "cmd", language: "sql", command: "create class Person extends V"}, (...)]
67
+ --
68
+ It was first used by ActiveOrient::Query.execute_queries
69
+ Later I (topofocus) discovered that some Queries are not interpretated correctly by #GetRecords but are submitted without Error via batch-processing.
70
+ For instance, this valid query
71
+ select expand(first_list[5].second_list[9]) from base where label = 9
72
+ can only be submitted via batch
73
+ ++
74
+ Parameters:
75
+
76
+ transaction: true|false Perform the batch as transaction
77
+ tolerate_error_code: /a regular expression/
78
+ Statements to execute are provided via block
79
+ These statements are translated to json and transmitted to the database. Example:
80
+
81
+ { type: "cmd",
82
+ language: 'sql',
83
+ command: "CREATE EDGE #{classname(o_class)} FROM #{from.to_orient} TO #{to.to_orient}"}
84
+
85
+ Multible statements are transmitted at once if the Block provides an Array of statements.
86
+
87
+
88
+ =end
89
+
90
+ def execute transaction: true, tolerated_error_code: nil, process_error: true, raw: nil # Set up for classes
91
+ batch = {transaction: transaction, operations: yield}
92
+ logger.progname= "Execute"
93
+ unless batch[:operations].blank?
94
+ batch[:operations] = {:type=>"cmd", :language=>"sql", :command=> batch[:operations]} if batch[:operations].is_a? String
95
+ batch[:operations] = [batch[:operations]] unless batch[:operations].is_a? Array
96
+ batch[:operations].compact!
97
+ # transaction is true only for multible statements
98
+ # batch[:transaction] = transaction & batch[:operations].size >1
99
+ begin
100
+ logger.debug{ batch[:operations].map{|y|y[:command]}.join("; ") }
101
+ response = @res["/batch/#{ActiveOrient.database}"].post batch.to_json
102
+ rescue RestClient::BadRequest => f
103
+ # extract the misspelled query in logfile and abort
104
+ sentence= JSON.parse( f.response)['errors'].last['content']
105
+ logger.fatal{ " BadRequest --> #{sentence.split("\n")[1]} " }
106
+ puts "Query not recognized"
107
+ puts sentence
108
+ raise
109
+ rescue RestClient::InternalServerError => e
110
+ logger.progname = 'RestOperations#Execute'
111
+ sentence= JSON.parse( e.response)['errors'].last['content']
112
+ if tolerated_error_code.present? && e.response =~ tolerated_error_code
113
+ logger.info{ "tolerated_error::#{e.message}"}
114
+ else
115
+ if process_error
116
+ # puts batch.to_json
117
+ # logger.error{e.response}
118
+ logger.error{sentence}
119
+ logger.error{ e.backtrace.map {|l| " #{l}\n"}.join }
120
+ # logger.error{e.message.to_s}
121
+ else
122
+ raise
123
+ end
124
+ end
125
+ end
126
+ if response.present? && response.code == 200
127
+ if response.body['result'].present?
128
+ result= JSON.parse(response.body)['result']
129
+ return result if raw.present?
130
+ result.map do |x|
131
+ if x.is_a? Hash
132
+ if x.has_key?('@class')
133
+ ActiveOrient::Model.orientdb_class(name: x['@class'], superclass: :find_ME ).new x
134
+ elsif x.has_key?('value')
135
+ x['value']
136
+ else # create a dummy class and fill with attributes from result-set
137
+ ActiveOrient::Model.orientdb_class(name: 'query' ).new x
138
+ end
139
+ end
140
+ end.compact # return_value
141
+ else
142
+ response.body
143
+ end
144
+ else
145
+ nil
146
+ end
147
+ end
148
+ end
149
+
150
+ end
@@ -0,0 +1,150 @@
1
+ module RestRead
2
+
3
+ ############# DATABASE #############
4
+
5
+ # Returns an Array with available Database-Names as Elements
6
+
7
+ def get_databases
8
+ JSON.parse(@res["/listDatabases"].get.body)['databases']
9
+ end
10
+
11
+ =begin
12
+ Returns an Array with (unmodified) Class-attribute-hash-Elements
13
+
14
+ get_classes 'name', 'superClass' returns
15
+ [ {"name"=>"E", "superClass"=>""},
16
+ {"name"=>"OFunction", "superClass"=>""},
17
+ {"name"=>"ORole", "superClass"=>"OIdentity"}
18
+ (...) ]
19
+ =end
20
+
21
+ def get_classes *attributes
22
+ begin
23
+ response = @res["/database/#{ActiveOrient.database}"].get
24
+ if response.code == 200
25
+ classes = JSON.parse(response.body)['classes']
26
+ unless attributes.empty?
27
+ classes.map{|y| y.select{|v,_| attributes.include?(v)}}
28
+ else
29
+ classes
30
+ end
31
+ else
32
+ []
33
+ end
34
+ rescue Exception => e
35
+ logger.progname = 'RestRead#GetClasses'
36
+ logger.error{e.message}
37
+ end
38
+ end
39
+
40
+
41
+ ############### CLASS ################
42
+
43
+ # Return a JSON of the property of a class
44
+
45
+ def get_class_properties o_class
46
+ JSON.parse(@res["/class/#{ActiveOrient.database}/#{classname(o_class)}"].get)
47
+ rescue => e
48
+ logger.error e.message
49
+ nil
50
+ end
51
+
52
+
53
+ def print_class_properties o_class
54
+ puts "Detected Properties for class #{classname(o_class)}"
55
+ rp = get_class_properties o_class
56
+ n = rp['name']
57
+ if rp['properties'].nil?
58
+ puts "No property available"
59
+ else
60
+ puts rp['properties'].map{|x| "\t"+[n+'.'+x['name'], x['type'],x['linkedClass']].compact.join("\t-> ")}.join("\n")
61
+ end
62
+ rescue NoMethodError
63
+ puts "Class #{o_class} not present in database"
64
+ end
65
+
66
+ ############## OBJECT #################
67
+
68
+ =begin
69
+ Retrieves a Record from the Database as ActiveOrient::Model::{class}
70
+ The argument can either be a rid (#[x}:{y}) or a link({x}:{y})
71
+ If no Record is found, nil is returned
72
+ =end
73
+
74
+ def get_record rid
75
+ begin
76
+ logger.progname = 'RestRead#GetRecord'
77
+ if rid.rid?
78
+ rid = rid[1..rid.length] if rid[0]=='#'
79
+ response = @res["/document/#{ActiveOrient.database}/#{rid}"].get
80
+ raw_data = JSON.parse(response.body) #.merge( "#no_links" => "#no_links" )
81
+ ActiveOrient::Model.orientdb_class(name: raw_data['@class'], superclass: :find_ME).new raw_data
82
+ else
83
+ logger.error { "Wrong parameter #{rid.inspect}. " }
84
+ nil
85
+ end
86
+ rescue RestClient::InternalServerError => e
87
+ if e.http_body.split(':').last =~ /was not found|does not exist in database/
88
+ nil
89
+ else
90
+ logger.error { "Something went wrong" }
91
+ logger.error { e.http_body.inspect }
92
+ raise
93
+ end
94
+ rescue RestClient::ResourceNotFound => e
95
+ logger.error { "Not data found" }
96
+ logger.error { e.message }
97
+ rescue Exception => e
98
+ logger.error { "Something went wrong" }
99
+ logger.error { "RID: #{rid} - #{e.message}" }
100
+ end
101
+ end
102
+ alias get_document get_record
103
+
104
+ =begin
105
+ Retrieves Records from a query
106
+ If raw is specified, the JSON-Array is returned, e.g.
107
+ {"@type"=>"d", "@rid"=>"#15:1", "@version"=>1, "@class"=>"DocumebntKlasse10", "con_id"=>343, "symbol"=>"EWTZ"}
108
+ Otherwise a ActiveModel-Instance of o_class is created and returned
109
+ =end
110
+
111
+ def get_records raw: false, query: nil, **args
112
+ query = OrientSupport::OrientQuery.new(args) if query.nil?
113
+ begin
114
+ logger.progname = 'RestRead#GetRecords'
115
+ url = "/query/#{ActiveOrient.database}/sql/" + query.compose(destination: :rest) + "/#{query.get_limit}"
116
+ # puts "URL"
117
+ # puts query.compose( destination: :rest).to_s
118
+ # puts url.to_s
119
+ response = @res[URI.encode(url)].get
120
+ JSON.parse(response.body)['result'].map do |record|
121
+ if raw
122
+ record
123
+ # query returns an anonymus class: Use the provided Block or the Dummy-Model MyQuery
124
+ elsif record['@class'].blank?
125
+ # puts "RECORD:\n"+record.inspect
126
+ block_given? ? yield.new(record) : ActiveOrient::Model.orientdb_class(name: 'query' ).new( record )
127
+ else
128
+ ActiveOrient::Model.orientdb_class(name: record['@class'], superclass: :find_ME).new record
129
+ end
130
+ end
131
+ # returns the JSON-Object
132
+
133
+
134
+ rescue RestClient::InternalServerError => e
135
+ response = JSON.parse(e.response)['errors'].pop
136
+ logger.error{ "Interbak Server ERROR" }
137
+ logger.error{response['content'].split(':').last}
138
+ rescue URI::InvalidURIError => e
139
+ logger.error{"Invalid URI detected"}
140
+ logger.error query.to_s
141
+ logger.info{"Trying batch processing"}
142
+ sql_cmd = -> (command){{type: "cmd", language: "sql", command: command}}
143
+ response = execute{[sql_cmd[query.to_s]]}
144
+ logger.info{"Success: to avoid this delay use ActiveOrient::Model#query_database instead"}
145
+ response
146
+ end
147
+ end
148
+ alias get_documents get_records
149
+
150
+ end
@@ -0,0 +1,111 @@
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 'cgi'
7
+ require 'rest-client'
8
+
9
+ module ActiveOrient
10
+
11
+ =begin
12
+ OrientDB performs queries to a OrientDB-Database
13
+ The communication is based on the ActiveOrient-API.
14
+ The OrientDB-Server is specified in config/connect.yml
15
+ A Sample:
16
+ :orientdb:
17
+ :server: localhost
18
+ :port: 2480
19
+ :database: working-database
20
+ :admin:
21
+ :user: admin-user
22
+ :pass: admin-password
23
+ =end
24
+
25
+ class OrientDB
26
+ include OrientSupport::Support
27
+ include OrientDbPrivate
28
+ include DatabaseUtils
29
+ include ClassUtils
30
+ include RestRead
31
+ include RestCreate
32
+ include RestChange
33
+ include RestOperations
34
+ include RestDelete
35
+
36
+ mattr_accessor :logger # borrowed from active_support
37
+ attr_reader :database # Used to read the working database
38
+
39
+ #### INITIALIZATION ####
40
+
41
+ =begin
42
+ Contructor: OrientDB is conventionally initialized.
43
+ Thus several instances pointing to the same or different databases can coexist
44
+
45
+ A simple
46
+ xyz = ActiveOrient::OrientDB.new
47
+ uses the database specified in the yaml-file »config/connect.yml« and connects
48
+ xyz = ActiveOrient::OrientDB.new database: my_fency_database
49
+ accesses the database »my_fency_database«. The database is created if its not existing.
50
+
51
+ *USECASE*
52
+ xyz = ActiveOrient::Model.orientdb = ActiveOrient::OrientDB.new
53
+ initialises the Database-Connection and publishes the Instance to any ActiveOrient::Model-Object
54
+ =end
55
+
56
+ def initialize database: nil, connect: true, preallocate: true
57
+ self.logger = Logger.new('/dev/stdout') unless logger.present?
58
+ # self.default_server = {
59
+ # :server => 'localhost',
60
+ # :port => 2480,
61
+ # :protocol => 'http',
62
+ # :user => 'root',
63
+ # :password => 'root',
64
+ # :database => 'temp'
65
+ # }.merge default_server.presence || {}
66
+ # @res = get_resource
67
+ ActiveOrient.database = database if database.present?
68
+ @res = get_resource
69
+ connect() if connect
70
+ database_classes # initialize @classes-array
71
+ ActiveOrient::Model.orientdb = self
72
+ ActiveOrient::Model.db = self
73
+ preallocate_classes if preallocate
74
+
75
+ end
76
+
77
+ def get_resource
78
+ login = [ActiveOrient.default_server[:user].to_s , ActiveOrient.default_server[:password].to_s]
79
+ server_adress = "http://#{ActiveOrient.default_server[:server]}:#{ActiveOrient.default_server[:port]}"
80
+ RestClient::Resource.new(server_adress, *login)
81
+ end
82
+
83
+ # Used to connect to the database
84
+
85
+ def connect
86
+ first_tentative = true
87
+ begin
88
+ database = ActiveOrient.database
89
+ logger.progname = 'OrientDB#Connect'
90
+ r = @res["/connect/#{database}"].get
91
+ if r.code == 204
92
+ logger.info{"Connected to database #{database}"}
93
+ true
94
+ else
95
+ logger.error{"Connection to database #{database} could NOT be established"}
96
+ nil
97
+ end
98
+ rescue RestClient::Unauthorized => e
99
+ if first_tentative
100
+ logger.info{"Database #{database} NOT present --> creating"}
101
+ first_tentative = false
102
+ create_database
103
+ retry
104
+ else
105
+ Kernel.exit
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end