module ::JdbcSpec module ActiveRecordExtensions def hsqldb_connection(config) config[:url] ||= "jdbc:hsqldb:#{config[:database]}" config[:driver] ||= "org.hsqldb.jdbcDriver" embedded_driver(config) end def h2_connection(config) config[:url] ||= "jdbc:h2:#{config[:database]}" config[:driver] ||= "org.h2.Driver" embedded_driver(config) end end module HSQLDB def self.column_selector [/hsqldb|\.h2\./i, lambda {|cfg,col| col.extend(::JdbcSpec::HSQLDB::Column)}] end def self.adapter_selector [/hsqldb|\.h2\./i, lambda {|cfg,adapt| adapt.extend(::JdbcSpec::HSQLDB)}] end module Column def type_cast(value) return nil if value.nil? || value =~ /^\s*null\s*$/i case type when :string then value when :integer then defined?(value.to_i) ? value.to_i : (value ? 1 : 0) when :primary_key then defined?(value.to_i) ? value.to_i : (value ? 1 : 0) when :float then value.to_f when :datetime then cast_to_date_or_time(value) when :timestamp then cast_to_time(value) when :binary then value.scan(/[0-9A-Fa-f]{2}/).collect {|v| v.to_i(16)}.pack("C*") when :time then cast_to_time(value) else value end end def cast_to_date_or_time(value) return value if value.is_a? Date return nil if value.blank? guess_date_or_time((value.is_a? Time) ? value : cast_to_time(value)) end def cast_to_time(value) return value if value.is_a? Time time_array = ParseDate.parsedate value time_array[0] ||= 2000; time_array[1] ||= 1; time_array[2] ||= 1; Time.send(ActiveRecord::Base.default_timezone, *time_array) rescue nil end def guess_date_or_time(value) (value.hour == 0 and value.min == 0 and value.sec == 0) ? Date.new(value.year, value.month, value.day) : value end private def simplified_type(field_type) case field_type when /longvarchar/i :text else super(field_type) end end # Override of ActiveRecord::ConnectionAdapters::Column def extract_limit(sql_type) # HSQLDB appears to return "LONGVARCHAR(0)" for :text columns, which # for AR purposes should be interpreted as "no limit" return nil if sql_type =~ /\(0\)/ super end end def modify_types(tp) tp[:primary_key] = "INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0) PRIMARY KEY" tp[:integer][:limit] = nil tp[:boolean][:limit] = nil # set text and float limits so we don't see odd scales tacked on # in migrations tp[:text][:limit] = nil tp[:float][:limit] = 17 tp[:string][:limit] = 255 tp[:datetime] = { :name => "DATETIME" } tp[:timestamp] = { :name => "DATETIME" } tp[:time] = { :name => "DATETIME" } tp[:date] = { :name => "DATETIME" } tp end def quote(value, column = nil) # :nodoc: return value.quoted_id if value.respond_to?(:quoted_id) case value when String if column && column.type == :binary "'#{quote_string(value).unpack("C*").collect {|v| v.to_s(16)}.join}'" else "'#{quote_string(value)}'" end else super end end def quote_string(str) str.gsub(/'/, "''") end def quoted_true '1' end def quoted_false '0' end def change_column(table_name, column_name, type, options = {}) #:nodoc: 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 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 def rename_table(name, new_name) execute "ALTER TABLE #{name} RENAME TO #{new_name}" end def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: log(sql,name) do @connection.execute_update(sql) end table = sql.split(" ", 4)[2] id_value || last_insert_id(table, nil) end def last_insert_id(table, sequence_name) Integer(select_value("SELECT IDENTITY() FROM #{table}")) end def add_limit_offset!(sql, options) #:nodoc: offset = options[:offset] || 0 bef = sql[7..-1] if limit = options[:limit] sql.replace "select limit #{offset} #{limit} #{bef}" elsif offset > 0 sql.replace "select limit #{offset} 0 #{bef}" end end # override to filter out system tables that otherwise end # up in db/schema.rb during migrations. JdbcConnection#tables # now takes an optional block filter so we can screen out # rows corresponding to system tables. HSQLDB names its # system tables SYSTEM.*, but H2 seems to name them without # any kind of convention def tables @connection.tables.select {|row| row.to_s !~ /^system_/i } end end end