require 'orientdb' require_relative "database_utils.rb" #common methods without rest.specific content require_relative "class_utils.rb" #common methods without rest.specific content require_relative "orientdb_private.rb" =begin EXPERIMENTAL CODE accessing the java-api through jruby =end module OrientDB class Document def update_attributes attributes attributes.each do |y,x| self[ y ] = x.to_orient end end end # class Document end module ActiveOrient # class Date # def proxy_object # java.util.Date.new year, month - 1, day, 0, 0, 0 # end # end # class DateTime # def proxy_object # java.util.Date.new year, month - 1, day, hour, min, sec # end # end class API include OrientSupport::Support include DatabaseUtils include ClassUtils include OrientDbPrivate include OrientDB mattr_accessor :logger # borrowed from active_support attr_reader :database # Used to read the working database #### INITIALIZATION #### def initialize database: nil, connect: true, preallocate: true self.logger = Logger.new('/dev/stdout') unless logger.present? ActiveOrient.database= database if database.present? connect() if connect ActiveOrient::Model.db = self preallocate_classes if preallocate end def db @db end # Used for the connection on the server # # Used to connect to the database def connect begin logger.progname = 'JavaApi#connect' @db = DocumentDatabase.connect("remote:#{ActiveOrient.default_server[:server]}/#{ActiveOrient.database}", ActiveOrient.default_server[:user], ActiveOrient.default_server[:password] ) rescue Java::ComOrientechnologiesOrientCoreException::OConfigurationException => e logger.fatal{ e.message} logger.fatal{ "ServerAdim not implemented in ActiveOrient#JavaApi "} # OrientDB::ServerAdmin("remote:localhost").connect( default_server[:user], default_server[:password] ) # OrientDB::ServerAdmin.createDatabase(@database, "document", "remote"); # OrientDB::ServerAdmin.close(); Kernel.exit end database_classes( requery:true ) # returns all allocated database_classes end def get_classes *attributes classes= @db.metadata.schema.classes.map{|x| { 'name' => x.name , 'superClass' => x.get_super_class.nil? ? '': x.get_super_class.name } } unless attributes.empty? classes.map{|y| y.select{|v,_| attributes.include?(v)}} else classes end end def create_classes classes , &b consts = allocate_classes_in_ruby( classes , &b ) all_classes = consts.is_a?( Array) ? consts.flatten : [consts] database_classes(requery: true) selected_classes = all_classes.map do | this_class | this_class unless database_classes(requery: true).include?( this_class.ref_name ) rescue nil end.compact.uniq command= selected_classes.map do | database_class | ## improper initialized ActiveOrient::Model-classes lack a ref_name class-variable next if database_class.ref_name.blank? c = if database_class.superclass == ActiveOrient::Model || database_class.superclass.ref_name.blank? puts "CREATE CLASS #{database_class.ref_name} " OClassImpl.create @db, database_class.ref_name else puts "CREATE CLASS #{database_class.ref_name} EXTENDS #{database_class.superclass.ref_name}" OClassImpl.create @db, database_class.ref_name, superClass: database_class.superclass.ref_name end end # update the internal class hierarchy database_classes requery: true # return all allocated classes, no matter whether they had to be created in the DB or not. # keep the format of the input-parameter consts.shift if block_given? && consts.is_a?( Array) # remove the first element # remove traces of superclass-allocations if classes.is_a? Hash consts = Hash[ consts ] consts.each_key{ |x| consts[x].delete_if{|y| y == x} if consts[x].is_a? Array } end consts end def delete_class o_class begin logger.progname = 'JavaApi#DeleteClass' @db.schema.drop_class classname(o_class) rescue Java::ComOrientechnologiesOrientCoreException::OSchemaException => e logger.error{ e.message } end database_classes requery: true end =begin Creates properties and optional an associated index as defined in the provided block create_properties(classname or class, properties as hash){index} The default-case create_properties(:my_high_sophisticated_database_class, con_id: {type: :integer}, details: {type: :link, linked_class: 'Contracts'}) do contract_idx: :notunique end A composite index create_properties(:my_high_sophisticated_database_class, con_id: {type: :integer}, symbol: {type: :string}) do {name: 'indexname', on: [:con_id, :details] # default: all specified properties type: :notunique # default: :unique } end supported types: { :bool => "BOOLEAN", :double => "BYTE", :datetime => "DATE", :float => "FLOAT", :decimal => "DECIMAL", :embedded_list => "EMBEDDEDLIST", :list => "EMBEDDEDLIST", :embedded_map => "EMBEDDEDMAP", :map => "EMBEDDEDMAP", :embedded_set => "EMBEDDEDSET", :set => "EMBEDDEDSET", :int => "INTEGER", :integer => "INTEGER", :link_list => "LINKLIST", :link_map => "LINKMAP", :link_set => "LINKSET", } =end def create_properties o_class, **all_properties, &b logger.progname = 'JavaApi#CreateProperties' ap = all_properties index = ap.is_a?(Hash) ? ap[:index] : nil created_properties = ap.map do |property, specification | field_type = ( specification.is_a?( Hash) ? specification[:type] : specification ).downcase.to_sym rescue :string if specification.is_a?(Hash) the_other_class = specification[:other_class].presence || specification[:linked_class] other_class = @db.get_class( the_other_class.to_sym ) if the_other_class.present? if other_class.present? @db.get_class(classname(o_class)).add property,[ field_type, other_class ], { :index => index } else @db.get_class(classname(o_class)).add property, field_type, { :index => index } end end end if block_given? attr = yield index_parameters = case attr when String, Symbol { name: attr } when Hash { name: attr.keys.first , type: attr.values.first, on: all_properties.keys.map(&:to_s) } else nil end create_index o_class, **index_parameters unless index_parameters.blank? end created_properties.size # return_value end def get_properties o_class @db.get_class(classname(o_class)).propertiesMap end def create_index o_class, name:, on: :automatic, type: :unique logger.progname = 'JavaApi#CreateIndex' begin c = @db.get_class( classname( o_class )) index = if on == :automatic nil # not implemented elsif on.is_a? Array c.createIndex name.to_s, INDEX_TYPES[type], *on else c.createIndex name.to_s, INDEX_TYPES[type], on end end end def create_record o_class, attributes: {} logger.progname = 'JavaApi#CreateRecord' attributes = yield if attributes.empty? && block_given? new_record = insert_document( classname(o_class), attributes.to_orient ) end alias create_document create_record def upsert o_class, set: {}, where: {} logger.progname = 'JavaApi#Upsert' if where.blank? new_record = create_record(o_class, attributes: set) yield new_record if block_given? # in case if insert execute optional block new_record # return_value else specify_return_value = block_given? ? "" : "return after @this" set.merge! where if where.is_a?( Hash ) # copy where attributes to set command = "Update #{classname(o_class)} set #{generate_sql_list( set ){','}} upsert #{specify_return_value} #{compose_where where}" result = @db.run_command command case result when Java::JavaUtil::ArrayList update_document result[0] when ActiveOrient::Model result # just return the result when String, Numeric the_record= get_records(from: o_class, where: where, limit: 1).pop if result.to_i == 1 # one dataset inserted, block is specified yield the_record end the_record # return_value else logger.error{ "Unexpected result form Query \n #{command} \n Result: #{result}" } end end end def get_records raw: false, query: nil, **args query = OrientSupport::OrientQuery.new(args) if query.nil? logger.progname = 'JavaApi#GetRecords' result = @db.custom query.compose result.map do |record| update_document record end end alias get_documents get_records # called by Model.autoload def get_record rid logger.progname = 'JavaApi#GetRecord' rid = "#"+ rid unless rid[0]=='#' record = @db.custom "select from #{rid}" if record.count.zero? logger.error{ "No record found for rid= #{rid}" } else yield( record[0] ) if block_given? update_document record[0] end end alias get_document get_record =begin executes a command as sql-query =end def execute transaction: true, tolerated_error_code: nil , process_error: true# Set up for classes batch = {transaction: transaction, operations: yield} logger.progname= "Execute" unless batch[:operations].blank? unless batch[:operations].is_a? Array batch[:operations] = [batch[:operations]] was_array = true else was_array = false end answer = batch[:operations].map do |command_record| return if command_record.blank? begin response = @db.run_command command_record.is_a?(Hash) ? command_record[:command] : command_record rescue Java::ComOrientechnologiesOrientCoreStorage::ORecordDuplicatedException => e # puts e.inspect # puts "GetMESSAGE: "+e.getMessage.split(/\r/).first # puts "GetComponentName: "+e.getComponentName.to_s # puts e.getMessage =~ tolerated_error_code # puts "----" if tolerated_error_code.present? && e.getMessage =~ tolerated_error_code logger.info{ "tolerated_error::#{ e.get_message.split(/\r/).first }"} next else # if process_error logger.progname = 'JavaApi#Execute' logger.error{ e } # else # puts e.inspect # raise ArgumentError, e, caller end end if response.is_a? Fixnum response else response.map do | r | if r.is_a? Document if r.rid.rid? update_document r else ActiveOrient::Model.orientdb_class( name: 'query').new r end else puts "Strange things happen in execute: #{r.inspect}" r.values end end # map response end # branch response_is_a end # map batch answer.pop if answer.size==1 && answer.first.is_a?(Array) end # unless end def delete_record *object_or_rid object_or_rid.map do |o| d= case o when String @db.custom "select from #{o}" if o.rid? when ActiveOrient::Model @db.custom "select from #{o.to_orient}" when Array o.map{|y| delete_record y } return o else o end if d.is_a? Java::ComOrientechnologiesOrientCoreSqlQuery::OConcurrentResultSet d.each &:delete else logger.progname = 'JavaApi#DeleteRecord' logger.error{ "Removal Failed: #{d.inspect} " } end end end =begin Transfer changes in attributes to document first and save the document No SQL involved =end def update rid, attributes=nil, version=nil record = ActiveOrient::Model.autoload_object rid.rid record.document.update_attributes attributes if attributes.present? record.document.save record.attributes.merge! attributes if attributes.present? record # return_value end # old code # get_record( rid.rid ) do | db_obj | # db_obj.update_attributes attributes ## puts db_obj.inspect # db_obj.save # end if rid.rid.present? # end #private def insert_document o_class, attributes logger.progname = 'JavaApi#InsertDocument' d = Document.new classname(o_class) d.update_attributes attributes d.save ActiveOrient::Model.get_model_class(o_class).new d rescue Java::ComOrientechnologiesOrientCoreException::ODatabaseException => e logger.fatal{ "Insert failed => #{d.inspect}"} logger.error{ "Parameter: Class: #{classname(o_class)} attributes: #{attributes.inspect}" } logger.fatal{ e } rescue Java::ComOrientechnologiesOrientCoreException::OSchemaException => e logger.error{ e } logger.error{ "Parameter: DB: #{@db.name}, Class: #{classname(o_class)} attributes: #{attributes.inspect}" } logger.error{ database_classes.inspect } end # returns a valid model-instance def update_document java_document if java_document.is_a? Document o_class = java_document.class_name java_document.save d = java_document ActiveOrient::Model.get_model_class(o_class).new java_document else logger.progname = 'JavaApi#UpdateDocument' logger.error{ "Wrong Parameter: #{java_document.inspect} "} end end #def def manipulate_relation record, method, array, items java_document = record.document method = method.to_s.downcase.to_sym case method when :add items.each{|x| java_document[array] << x} when :remove items.each{|x| java_document[array].delete x} else end java_document.save end end end