require 'bigdecimal' require 'active_record/connection_adapters/abstract/schema_definitions' module ::ArJdbc module NuoDB def self.column_selector [/nuodb/i, lambda { |cfg, col| col.extend(::ArJdbc::NuoDB::ColumnExtensions) }] end def self.arel2_visitors(config) {}.tap { |v| %w(nuodb).each { |a| v[a] = ::Arel::Visitors::NuoDB } } end # COLUMNS ================================================================ module ColumnExtensions # Maps NuoDB types to logical rails types. def simplified_type(field_type) case field_type when /bit/i then :boolean when /timestamp/i then :timestamp when /time/i then :time when /date/i then :date else super end end def extract_limit(sql_type) case sql_type when /^smallint/i 2 when /^int/i 4 when /^bigint/i 8 else super end end end # ADAPTER SUPPORT ======================================================== ADAPTER_NAME = 'NuoDB' NATIVE_DATABASE_TYPES = { # generic rails types... :binary => {:name => 'binary'}, :boolean => {:name => 'boolean'}, :date => {:name => 'date'}, :datetime => {:name => 'timestamp'}, :decimal => {:name => 'decimal'}, :float => {:name => 'float', :limit => 8}, :integer => {:name => 'integer'}, :primary_key => 'int not null generated by default as identity primary key', :string => {:name => 'varchar', :limit => 255}, :text => {:name => 'varchar', :limit => 255}, :time => {:name => 'time'}, :timestamp => {:name => 'timestamp'}, # nuodb specific types... :char => {:name => 'char'}, :numeric => {:name => 'numeric(20)'}, } # Maps logical rails types to NuoDB types. def native_database_types NATIVE_DATABASE_TYPES end def adapter_name ADAPTER_NAME end def supports_savepoints? true end def supports_ddl_transactions? true end def supports_index_sort_order? true end def create_savepoint execute("SAVEPOINT #{current_savepoint_name}") end def rollback_to_savepoint execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") end def release_savepoint execute("RELEASE SAVEPOINT #{current_savepoint_name}") end def modify_types(tp) tp[:primary_key] = 'int not null generated always primary key' tp[:boolean] = {:name => 'boolean'} tp[:date] = {:name => 'date', :limit => nil} tp[:datetime] = {:name => 'timestamp', :limit => nil} tp[:decimal] = {:name => 'decimal'} tp[:integer] = {:name => 'int', :limit => 4} tp[:string] = {:name => 'string'} tp[:time] = {:name => 'time', :limit => nil} tp[:timestamp] = {:name => 'timestamp', :limit => nil} tp end protected def translate_exception(exception, message) # future translations here... super end public # QUOTING ================================================================ def quote(value, column = nil) case value when TrueClass, FalseClass value.to_s else super end end def quote_column_name(name) "`#{name.to_s.gsub('`', '``')}`" end def quote_table_name(name) quote_column_name(name).gsub('.', '`.`') end def type_cast(value) return super unless value == true || value == false value ? true : false end def quoted_true "'true'" end def quoted_false "'false'" end def quoted_date(value) if value.acts_like?(:time) zone_conversion_method = :getutc if value.respond_to?(zone_conversion_method) value = value.send(zone_conversion_method) end end value.to_s(:db) end # COMPATIBILITY ========================================================== # SCHEMA STATEMENTS ====================================================== def columns(table_name, name=nil) @connection.columns_internal(table_name.to_s, name, nuodb_schema) end # maps logical rails types to nuodb-specific data types. def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type.to_s when 'integer' return 'integer' unless limit case limit when 1..2 'smallint' when 3..4 'integer' when 5..8 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.") end when 'timestamp' column_type_sql = 'timestamp' unless precision.nil? case precision when 0..9 column_type_sql << "(#{precision})" else nil end end column_type_sql when 'time' column_type_sql = 'time' unless precision.nil? case precision when 0..9 column_type_sql << "(#{precision})" else nil end end column_type_sql else super end end def rename_column(table_name, column_name, new_column_name) raise NotImplementedError, "rename_column is not implemented" end def rename_table(table_name, new_name) raise NotImplementedError, "rename_table is not implemented" end def recreate_database(name, options = {}) #:nodoc: drop_database(name) create_database(name, options) end def create_database(name, options = {}) #:nodoc: puts "create_database" end def drop_database(name) #:nodoc: puts "drop_database" end # DATABASE STATEMENTS ==================================================== def exec_insert(sql, name, binds) sql = substitute_binds(sql, binds) @connection.execute_insert(sql) end LOST_CONNECTION_ERROR_MESSAGES = [ "End of stream reached", "Broken pipe"] # Monkey patch the execute method as reconnect is broken in the underlying # Rails infrastructure; see these bug numbers and references: # # - https://github.com/jruby/activerecord-jdbc-adapter/issues/232 # - https://github.com/jruby/activerecord-jdbc-adapter/issues/237 def execute(sql, name = nil, binds = []) tries ||= 2 super rescue ActiveRecord::StatementInvalid => exception if LOST_CONNECTION_ERROR_MESSAGES.any? { |msg| exception.message =~ /#{msg}/ } reconnect! retry unless (tries -= 1).zero? end raise end # CONNECTION POOL ======================================================== def primary_keys(table) @connection.primary_keys(qualify_table(table)) end private def qualify_table(table) if (table.include? '.') || @config[:schema].blank? table else nuodb_schema + '.' + table end end def nuodb_schema config[:schema] || '' end end end module ActiveRecord::ConnectionAdapters class NuoDBColumn < JdbcColumn include ArJdbc::NuoDB::ColumnExtensions def initialize(name, *args) if Hash === name super else super(nil, name, *args) end end def call_discovered_column_callbacks(*) end end class NuoDBAdapter < JdbcAdapter include ArJdbc::NuoDB def initialize(*args) super end def jdbc_column_class ActiveRecord::ConnectionAdapters::NuoDBColumn end end end