active-orient 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
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