ArJdbc.load_java_part :HSQLDB require 'arjdbc/hsqldb/explain_support' require 'arjdbc/hsqldb/schema_creation' # AR 4.x require 'arel/visitors/hsqldb' module ArJdbc module HSQLDB include ExplainSupport # @see ActiveRecord::ConnectionAdapters::JdbcColumn#column_types def self.column_selector [ /hsqldb/i, lambda { |config, column| column.extend(Column) } ] end # @see ActiveRecord::ConnectionAdapters::JdbcColumn module Column private def extract_limit(sql_type) limit = super case @sql_type = sql_type.downcase when /^tinyint/i then @sql_type = 'tinyint'; limit = 1 when /^smallint/i then @sql_type = 'smallint'; limit = 2 when /^bigint/i then @sql_type = 'bigint'; limit = 8 when /^double/i then @sql_type = 'double'; limit = 8 when /^real/i then @sql_type = 'real'; limit = 8 # NOTE: once again we get incorrect "limits" from HypesSQL's JDBC # thus yet again we need to fix incorrectly detected limits : when /^integer/i then @sql_type = 'integer'; limit = 4 when /^float/i then @sql_type = 'float'; limit = 8 when /^decimal/i then @sql_type = 'decimal'; when /^datetime/i then @sql_type = 'datetime'; limit = nil when /^timestamp/i then @sql_type = 'timestamp'; limit = nil when /^time/i then @sql_type = 'time'; limit = nil when /^date/i then @sql_type = 'date'; limit = nil else # HSQLDB appears to return "LONGVARCHAR(0)" for :text columns, # which for AR purposes should be interpreted as "no limit" : limit = nil if sql_type =~ /\(0\)$/ end limit end def simplified_type(field_type) case field_type when /^nvarchar/i then :string when /^character/i then :string when /^longvarchar/i then :text when /int/i then :integer # TINYINT, SMALLINT, BIGINT, INT when /real|double/i then :float when /^bit/i then :boolean when /binary/i then :binary # VARBINARY, LONGVARBINARY else super end end # Post process default value from JDBC into a Rails-friendly format (columns{-internal}) def default_value(value) # JDBC returns column default strings with actual single quotes around the value. return $1 if value =~ /^'(.*)'$/ value end end ADAPTER_NAME = 'HSQLDB'.freeze def adapter_name ADAPTER_NAME end NATIVE_DATABASE_TYPES = { :primary_key => "integer GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY", :string => { :name => "varchar", :limit => 255 }, # :limit => 2147483647 :text => { :name => "clob" }, :binary => { :name => "blob" }, :boolean => { :name => "boolean" }, # :name => "tinyint", :limit => 1 :bit => { :name=>"bit" }, # stored as 0/1 on HSQLDB 2.2 (translates true/false) :integer => { :name => "integer", :limit => 4 }, :decimal => { :name => "decimal" }, # :limit => 2147483647 :numeric => { :name => "numeric" }, # :limit => 2147483647 # NOTE: fix incorrectly detected limits : :tinyint => { :name => "tinyint", :limit => 1 }, :smallint => { :name => "smallint", :limit => 2 }, :bigint => { :name => "bigint", :limit => 8 }, :float => { :name => "float" }, :double => { :name => "double", :limit => 8 }, :real => { :name => "real", :limit => 8 }, :date => { :name=>"date" }, :time => { :name=>"time" }, :timestamp => { :name=>"timestamp" }, :datetime => { :name=>"timestamp" }, :other => { :name=>"other" }, # NOTE: would be great if AR allowed as to refactor as : # t.column :string, :ignorecase => true :character => { :name => "character" }, :varchar_ignorecase => { :name => "varchar_ignorecase" }, } # @override def native_database_types NATIVE_DATABASE_TYPES end # @override def quote(value, column = nil) return value.quoted_id if value.respond_to?(:quoted_id) return value if sql_literal?(value) case value when String column_type = column && column.type if column_type == :binary "X'#{value.unpack("H*")[0]}'" elsif column_type == :integer || column.respond_to?(:primary) && column.primary && column.klass != String value.to_i.to_s else "'#{quote_string(value)}'" end when Time column_type = column && column.type if column_type == :time "'#{value.strftime("%H:%M:%S")}'" #elsif column_type == :timestamp # || column_type == :datetime #value = ::ActiveRecord::Base.default_timezone == :utc ? value.getutc : value.getlocal #"'#{value.strftime("%Y-%m-%d %H:%M:%S")}.#{sprintf("%06d", value.usec)}'" else super end else super end end # Quote date/time values for use in SQL input. # Includes microseconds if the value is a Time responding to usec. # @override def quoted_date(value) if value.acts_like?(:time) && value.respond_to?(:usec) usec = sprintf("%06d", value.usec) value = ::ActiveRecord::Base.default_timezone == :utc ? value.getutc : value.getlocal "#{value.strftime("%Y-%m-%d %H:%M:%S")}.#{usec}" else super end end if ::ActiveRecord::VERSION::MAJOR >= 3 # @override def quote_column_name(name) name = name.to_s if name =~ /[-]/ %Q{"#{name.upcase}"} else name end end # @override def add_column(table_name, column_name, type, options = {}) add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" add_column_options!(add_column_sql, options) execute(add_column_sql) end unless const_defined? :SchemaCreation # @override def change_column(table_name, column_name, type, options = {}) execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} #{type_to_sql(type, options[:limit])}" end def change_column_default(table_name, column_name, default) #:nodoc: execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{quote(default)}" end # @override def rename_column(table_name, column_name, new_column_name) #:nodoc: execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} RENAME TO #{new_column_name}" end # @override def type_to_sql(type, limit = nil, precision = nil, scale = nil) return super if defined?(::Jdbc::H2) || type.to_s != 'integer' || limit == nil type end # @override def rename_table(name, new_name) execute "ALTER TABLE #{name} RENAME TO #{new_name}" end # @note AR API since 4.2 def truncate(table_name, name = nil) execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name end def last_insert_id identity = select_value("CALL IDENTITY()") Integer(identity.nil? ? 0 : identity) end # @private def _execute(sql, name = nil) result = super self.class.insert?(sql) ? last_insert_id : result end private :_execute # @note Only used with (non-AREL) ActiveRecord **2.3**. # @see Arel::Visitors::HSQLDB#limit_offset def add_limit_offset!(sql, options) if sql =~ /^select/i offset = options[:offset] || 0 if limit = options[:limit] sql.replace "SELECT LIMIT #{offset} #{limit} #{sql[7..-1]}" elsif offset > 0 sql.replace "SELECT LIMIT #{offset} 0 #{sql[7..-1]}" end end end if ::ActiveRecord::VERSION::MAJOR < 3 # @override def empty_insert_statement_value # on HSQLDB only work with tables that have a default value for each # and every column ... you'll need to avoid `Model.create!` on 4.0 'DEFAULT VALUES' end # We filter out HSQLDB's system tables (named "SYSTEM.*"). # @override def tables @connection.tables.select { |row| row.to_s !~ /^system_/i } end # @override def remove_index(table_name, options = {}) execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}" end # @override def supports_views?; true end # @override def supports_foreign_keys?; true end # @override def structure_dump execute('SCRIPT').map do |result| # [ { 'command' => SQL }, { 'command' ... }, ... ] case sql = result.first[1] # ['command'] when /CREATE USER SA PASSWORD DIGEST .*?/i then nil when /CREATE SCHEMA PUBLIC AUTHORIZATION DBA/i then nil when /GRANT DBA TO SA/i then nil else sql end end.compact.join("\n\n") end # @see #structure_dump def structure_load(dump) dump.each_line("\n\n") { |ddl| execute(ddl) } end def shutdown execute 'SHUTDOWN' end # @private def recreate_database(name = nil, options = {}) drop_database(name) create_database(name, options) end # @private def create_database(name = nil, options = {}); end # @private def drop_database(name = nil) execute('DROP SCHEMA PUBLIC CASCADE') end end end module ActiveRecord::ConnectionAdapters class HsqldbAdapter < JdbcAdapter include ArJdbc::HSQLDB def arel_visitor # :nodoc: Arel::Visitors::HSQLDB end end end