module ArJdbc module Firebird # @private def self.extended(adapter); initialize!; end # @private @@_initialized = nil # @private def self.initialize! return if @@_initialized; @@_initialized = true require 'arjdbc/util/serialized_attributes' Util::SerializedAttributes.setup /blob/i end # @see ActiveRecord::ConnectionAdapters::JdbcColumn#column_types def self.column_selector [ /firebird/i, lambda { |cfg, column| column.extend(Column) } ] end # @see ActiveRecord::ConnectionAdapters::JdbcColumn module Column def default_value(value) return nil unless value if value =~ /^\s*DEFAULT\s+(.*)\s*$/i return $1 unless $1.upcase == 'NULL' end end private def simplified_type(field_type) case field_type when /timestamp/i then :datetime when /^smallint/i then :integer when /^bigint|int/i then :integer when /^double/i then :float # double precision when /^decimal/i then extract_scale(field_type) == 0 ? :integer : :decimal when /^char\(1\)$/i then Firebird.emulate_booleans? ? :boolean : :string when /^char/i then :string when /^blob\ssub_type\s(\d)/i return :binary if $1 == '0' return :text if $1 == '1' else super end end end # @see ArJdbc::ArelHelper::ClassMethods#arel_visitor_type def self.arel_visitor_type(config = nil) require 'arel/visitors/firebird'; ::Arel::Visitors::Firebird end # @deprecated no longer used def self.arel2_visitors(config = nil) { 'firebird' => arel_visitor_type, 'firebirdsql' => arel_visitor_type } end # @private @@emulate_booleans = true # Boolean emulation can be disabled using : # # ArJdbc::Firebird.emulate_booleans = false # def self.emulate_booleans?; @@emulate_booleans; end # @deprecated Use {#emulate_booleans?} instead. def self.emulate_booleans; @@emulate_booleans; end # @see #emulate_booleans? def self.emulate_booleans=(emulate); @@emulate_booleans = emulate; end @@update_lob_values = true # Updating records with LOB values (binary/text columns) in a separate # statement can be disabled using : # # ArJdbc::Firebird.update_lob_values = false def self.update_lob_values?; @@update_lob_values; end # @see #update_lob_values? def self.update_lob_values=(update); @@update_lob_values = update; end # @see #update_lob_values? def update_lob_values?; Firebird.update_lob_values?; end # @see #quote # @private BLOB_VALUE_MARKER = "''" ADAPTER_NAME = 'Firebird'.freeze def adapter_name ADAPTER_NAME end NATIVE_DATABASE_TYPES = { :primary_key => "integer not null primary key", :string => { :name => "varchar", :limit => 255 }, :text => { :name => "blob sub_type text" }, :integer => { :name => "integer" }, :float => { :name => "float" }, :datetime => { :name => "timestamp" }, :timestamp => { :name => "timestamp" }, :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "blob" }, :boolean => { :name => 'char', :limit => 1 }, :numeric => { :name => "numeric" }, :decimal => { :name => "decimal" }, :char => { :name => "char" }, } def native_database_types NATIVE_DATABASE_TYPES end def type_to_sql(type, limit = nil, precision = nil, scale = nil) case type when :integer case limit when nil then 'integer' when 1..2 then 'smallint' when 3..4 then 'integer' when 5..8 then 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}. "<< "Use a NUMERIC with PRECISION 0 instead.") end when :float if limit.nil? || limit <= 4 'float' else 'double precision' end else super end end # Does this adapter support migrations? def supports_migrations? true end # Can this adapter determine the primary key for tables not attached # to an Active Record class, such as join tables? def supports_primary_key? true end # Does this adapter support using DISTINCT within COUNT? def supports_count_distinct? true end # Does this adapter support DDL rollbacks in transactions? That is, would # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL, # SQL Server, and others support this. MySQL and others do not. def supports_ddl_transactions? false end # Does this adapter restrict the number of IDs you can use in a list. # Oracle has a limit of 1000. def ids_in_list_limit 1499 end def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) execute(sql, name, binds) id_value end def add_limit_offset!(sql, options) if options[:limit] limit_string = "FIRST #{options[:limit]}" limit_string << " SKIP #{options[:offset]}" if options[:offset] sql.sub!(/\A(\s*SELECT\s)/i, '\&' + limit_string + ' ') end end # Should primary key values be selected from their corresponding # sequence before the insert statement? # @see #next_sequence_value # @override def prefetch_primary_key?(table_name = nil) return true if table_name.nil? table_name = table_name.to_s columns(table_name).count { |column| column.primary } == 1 end IDENTIFIER_LENGTH = 31 # usual DB meta-identifier: 31 chars maximum def table_alias_length; IDENTIFIER_LENGTH; end def table_name_length; IDENTIFIER_LENGTH; end def index_name_length; IDENTIFIER_LENGTH; end def column_name_length; IDENTIFIER_LENGTH; end def default_sequence_name(table_name, column = nil) # TODO: remove schema prefix if present (before truncating) "#{table_name.to_s[0, IDENTIFIER_LENGTH - 4]}_seq" end # Set the sequence to the max value of the table's column. def reset_sequence!(table, column, sequence = nil) max_id = select_value("SELECT max(#{column}) FROM #{table}") execute("ALTER SEQUENCE #{default_sequence_name(table, column)} RESTART WITH #{max_id}") end def next_sequence_value(sequence_name) select_one("SELECT GEN_ID(#{sequence_name}, 1 ) FROM RDB$DATABASE;")["gen_id"] end def create_table(name, options = {}) super(name, options) execute "CREATE GENERATOR #{name}_seq" end def rename_table(name, new_name) execute "RENAME #{name} TO #{new_name}" execute "UPDATE RDB$GENERATORS SET RDB$GENERATOR_NAME='#{new_name}_seq' WHERE RDB$GENERATOR_NAME='#{name}_seq'" rescue nil end def drop_table(name, options = {}) super(name) execute "DROP GENERATOR #{name}_seq" rescue nil end def change_column(table_name, column_name, type, options = {}) execute "ALTER TABLE #{table_name} ALTER #{column_name} TYPE #{type_to_sql(type, options[:limit])}" end def rename_column(table_name, column_name, new_column_name) execute "ALTER TABLE #{table_name} ALTER #{column_name} TO #{new_column_name}" end def remove_index(table_name, options) execute "DROP INDEX #{index_name(table_name, options)}" end # @override def quote(value, column = nil) return value.quoted_id if value.respond_to?(:quoted_id) return value if sql_literal?(value) type = column && column.type # BLOBs are updated separately by an after_save trigger. if type == :binary || type == :text if update_lob_values? return value.nil? ? "NULL" : BLOB_VALUE_MARKER else return "'#{quote_string(value)}'" end end case value when String, ActiveSupport::Multibyte::Chars value = value.to_s if type == :integer value.to_i.to_s elsif type == :float value.to_f.to_s else "'#{quote_string(value)}'" end when NilClass then 'NULL' when TrueClass then (type == :integer ? '1' : quoted_true) when FalseClass then (type == :integer ? '0' : quoted_false) when Float, Fixnum, Bignum then value.to_s # BigDecimals need to be output in a non-normalized form and quoted. when BigDecimal then value.to_s('F') when Symbol then "'#{quote_string(value.to_s)}'" else if type == :time && value.acts_like?(:time) return "'#{get_time(value).strftime("%H:%M:%S")}'" end if type == :date && value.acts_like?(:date) return "'#{value.strftime("%Y-%m-%d")}'" end super end end # @override def quoted_date(value) if value.acts_like?(:time) && value.respond_to?(:usec) usec = sprintf "%04d", (value.usec / 100.0).round 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_string(string) string.gsub(/'/, "''") end # @override def quoted_true quote(1) end # @override def quoted_false quote(0) end # @override def quote_table_name_for_assignment(table, attr) quote_column_name(attr) end if ::ActiveRecord::VERSION::MAJOR >= 4 # @override def quote_column_name(column_name) column_name = column_name.to_s %Q("#{column_name =~ /[[:upper:]]/ ? column_name : column_name.upcase}") end end FireBird = Firebird end