Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #4093: FrontBaseAdaptor.patch

File FrontBaseAdaptor.patch, 43.5 kB (added by mlaster@metavillage.com, 2 years ago)

Latest patch as of revisioin 4212

  • activerecord/Rakefile

    old new  
    2727 
    2828# Run the unit tests 
    2929 
    30 for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase
     30for adapter in %w( mysql postgresql sqlite sqlite3 firebird sqlserver sqlserver_odbc db2 oracle sybase openbase frontbase
    3131  Rake::TestTask.new("test_#{adapter}") { |t| 
    3232    t.libs << "test" << "test/connections/native_#{adapter}" 
    3333    t.pattern = "test/*_test{,_#{adapter}}.rb" 
  • activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb

    old new  
     1require 'active_record/connection_adapters/abstract_adapter' 
     2require 'frontbase' 
     3 
     4module ActiveRecord 
     5 
     6### Hack around missing functionality 
     7  module Calculations 
     8    module ClassMethods       
     9      def construct_calculation_sql(aggregate, aggregate_alias, options) #:nodoc: 
     10        scope = scope(:find) 
     11        sql  = "SELECT #{aggregate} AS #{aggregate_alias}" 
     12        sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] 
     13        sql << " FROM #{table_name} " 
     14        add_joins!(sql, options, scope) 
     15        add_conditions!(sql, options[:conditions], scope) 
     16      
     17        # FrontBase requires a column reference for the GROUP BY and HAVING clauses 
     18        # Example: 'SELECT sum(credit_limit) AS sum_credit_limit, firm_id AS firm_id FROM accounts GROUP BY firm_id HAVING sum_credit_limit > 50' 
     19        sql << " GROUP BY #{options[:group_alias]}" if options[:group] 
     20        sql << " HAVING #{options[:having].gsub(aggregate,aggregate_alias)}" if options[:group] && options[:having] 
     21        sql << " ORDER BY #{options[:order]}" if options[:order] 
     22        add_limit!(sql, options) 
     23        sql 
     24      end 
     25    end 
     26  end     
     27   
     28  class Base 
     29 
     30    # FrontBase only supports one unnamed sequence per table 
     31    def self.set_sequence_name( value=nil, &block ) 
     32    end 
     33     
     34    # Establishes a connection to the database that's used by all Active Record objects. 
     35    def self.frontbase_connection(config) # :nodoc: 
     36      config = config.symbolize_keys 
     37      database     = config[:database] 
     38      port         = config[:port] 
     39      host         = config[:host] 
     40      username     = config[:username] 
     41      password     = config[:password] 
     42      dbpassword   = config[:dbpassword] 
     43      session_name = config[:session_name] 
     44 
     45      if dbpassword == nil 
     46        dbpassword = "" 
     47      end 
     48       
     49      # Turn off colorization since it make tail/less output difficult 
     50      self.colorize_logging = false; 
     51       
     52       
     53      connection = FBSQL_Connect.connect(host, port, database, username, password, dbpassword,session_name) 
     54      ConnectionAdapters::FrontBaseAdapter.new(connection, logger, [host, port, database, username, password, dbpassword, session_name], config) 
     55    end         
     56  end 
     57   
     58  module ConnectionAdapters 
     59 
     60    class FrontBaseColumn < Column #:nodoc: 
     61      attr_reader :fb_autogen 
     62       
     63      def initialize(base, name, type, typename, limit, precision, scale, default, nullable) 
     64         
     65        @base, @name = base, name 
     66        @type = simplified_type(type,typename,limit) 
     67        @limit = limit 
     68        @precision, @scale = precision, scale 
     69        @default = default 
     70        @null = nullable == "YES" ? true : false 
     71        @text    = [:string, :text].include? @type 
     72        @number  = [:float, :integer].include? @type 
     73        @fb_autogen = false 
     74        if @default 
     75          @default.gsub!(/^'(.*)'$/,'\1') if @text 
     76          @fb_autogen =  @default.include?("SELECT UNIQUE FROM") 
     77          if @type == :boolean then 
     78            @default = (@default == "TRUE") 
     79          elsif @type == :binary 
     80            if @default != "X''" 
     81              buffer = "" 
     82              @default.scan(/../) { |h| buffer << h.hex.chr } 
     83              @default = buffer 
     84            else 
     85              @default = "" 
     86            end 
     87          else 
     88            @default = type_cast(@default) 
     89          end 
     90        end 
     91      end 
     92       
     93      def self.hex_encode(value) 
     94        retvalue = "" 
     95        value.each_byte do |b| 
     96          retvalue << sprintf("%02X", b) 
     97        end 
     98        retvalue 
     99      end 
     100 
     101      def self.hex_decode(value) 
     102        retvalue = "" 
     103        value.scan(/../) do |h| 
     104          c = h.hex 
     105          retvalue << c.chr 
     106        end 
     107        retvalue 
     108      end 
     109 
     110      private 
     111      def simplified_type(field_type, type_name,limit) 
     112        ret_type = :string 
     113#         puts "typecode: [#{field_type}] [#{type_name}]" 
     114        case field_type 
     115          when 1 then ret_type = :boolean # BOOLEAN 
     116          when 2 then ret_type = :integer # INTEGER 
     117          when 4 then ret_type = :float   # FLOAT 
     118          when 10 then ret_type = :string # CHARACTER VARYING 
     119          when 11 then ret_type = :binary 
     120          when 12 then ret_type = :binary 
     121          when 13 then ret_type = :date 
     122          when 14 then ret_type = :time 
     123          when 16 then ret_type = :timestamp 
     124          when 22 then ret_type = :integer # TINYINT 
     125          else 
     126            puts "ERROR: Unknown typecode: [#{field_type}] [#{type_name}]" 
     127        end 
     128        ret_type 
     129      end 
     130    end 
     131 
     132    class FrontBaseAdapter < AbstractAdapter 
     133       
     134      def initialize(connection, logger, connection_options, config) 
     135        super(connection, logger) 
     136        @connection_options, @config = connection_options, config 
     137        @transaction_mode = :pessimistic 
     138         
     139        # threaded_connections_test.rb will fail unless we set the session 
     140        # to optimistic locking mode 
     141#         set_pessimistic_transactions 
     142#         execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" 
     143      end 
     144 
     145      # Returns the human-readable name of the adapter.  Use mixed case - one 
     146      # can always use downcase if needed. 
     147      def adapter_name #:nodoc: 
     148        'FrontBase' 
     149      end 
     150 
     151      # Does this adapter support migrations?  Backend specific, as the 
     152      # abstract adapter always returns +false+. 
     153      def supports_migrations? #:nodoc: 
     154        true 
     155      end 
     156 
     157      def native_database_types #:nodoc 
     158        # !!! Lookup actual FB datatypes 
     159        { 
     160          :primary_key => "INTEGER DEFAULT UNIQUE PRIMARY KEY", 
     161          :string      => { :name => "VARCHAR", :limit => 255 }, 
     162          :text        => { :name => "VARCHAR", :limit => 1073741824 }, 
     163          :integer     => { :name => "INTEGER" }, 
     164          :float       => { :name => "FLOAT" }, 
     165          :datetime    => { :name => "TIMESTAMP" }, 
     166          :timestamp   => { :name => "TIMESTAMP" }, 
     167          :time        => { :name => "TIME" }, 
     168          :date        => { :name => "DATE" }, 
     169          :binary      => { :name => "BYTE VARYING" }, 
     170          :boolean     => { :name => "BOOLEAN" } 
     171        } 
     172      end 
     173 
     174 
     175      # QUOTING ================================================== 
     176 
     177      # Quotes the column value to help prevent 
     178      # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. 
     179      def quote(value, column = nil) 
     180        retvalue = "<INVALID>" 
     181    
     182        # If a column was passed in, use column type information 
     183        if ! value.nil? 
     184          if column 
     185            case column.type 
     186              when :string, :text 
     187                if value.kind_of?(String) 
     188                  retvalue = "'#{quote_string(value.to_s)}'" # ' (for ruby-mode) 
     189                else 
     190                  retvalue = "'#{quote_string(value.to_yaml)}'" 
     191                end 
     192              when :integer 
     193                if value.kind_of?(TrueClass) 
     194                  retvalue = '1' 
     195                elsif value.kind_of?(FalseClass) 
     196                  retvalue = '0' 
     197                else 
     198                  retvalue = value.to_s 
     199                end 
     200              when :float 
     201                retvalue = value.to_s 
     202              when :datetime, :timestamp 
     203                retvalue = "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" 
     204              when :time 
     205                retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     206              when :date 
     207                retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     208              when :binary 
     209                s = value.unpack("H*")[0] 
     210                retvalue = "X'#{s}'" 
     211              when :boolean 
     212                retvalue = quoted_true if value.kind_of?(TrueClass) 
     213                retvalue = quoted_false if value.kind_of?(FalseClass) 
     214              else 
     215                raise "*** UNKNOWN TYPE: #{column.type.inspect}" 
     216            end # case 
     217          # Since we don't have column type info, make a best guess based 
     218          # on the Ruby class of the value 
     219          else 
     220            case value 
     221              when String 
     222                if column && column.type == :binary 
     223                  s = value.unpack("H*")[0] 
     224                  retvalue = "X'#{s}'" 
     225                elsif column && [:integer, :float].include?(column.type)  
     226                  retvalue = value.to_s 
     227                else 
     228                  retvalue = "'#{quote_string(value)}'" # ' (for ruby-mode) 
     229                end 
     230              when NilClass              then retvalue = "NULL" 
     231              when TrueClass             then retvalue = (column && column.type == :integer ? '1' : quoted_true) 
     232              when FalseClass            then retvalue = (column && column.type == :integer ? '0' : quoted_false) 
     233              when Float, Fixnum, Bignum then retvalue = value.to_s 
     234              when Time, Date, DateTime 
     235                if column 
     236                  case column.type 
     237                    when :date then retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     238                    when :time then retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     239                    when :timestamp then retvalue = "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" 
     240                  else 
     241                    raise NotImplementedError, "Unknown column type!" 
     242                  end # case 
     243                else # Column wasn't passed in, so try to guess the right type 
     244                  if value.kind_of? Date 
     245                    retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     246                  else 
     247                    if value.hour == 0 && value.min == 0 && value.sec == 0 
     248                      retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     249                    else 
     250                      retvalue = "TIMESTAMP '#{quoted_date(value)}'" 
     251                    end 
     252                  end  
     253                end #if column 
     254              else "'#{quote_string(value.to_yaml)}'" 
     255            end #case 
     256          end 
     257        else 
     258          retvalue = "NULL" 
     259        end 
     260          
     261      retvalue 
     262      end # def 
     263 
     264      # Quotes a string, escaping any ' (single quote) characters. 
     265      def quote_string(s) 
     266        s.gsub(/'/, "''") # ' (for ruby-mode) 
     267      end 
     268 
     269      def quote_column_name(name) #:nodoc: 
     270        "\"#{name}\"" 
     271      end 
     272 
     273      def quoted_true 
     274        "true" 
     275      end 
     276       
     277      def quoted_false 
     278        "false" 
     279      end 
     280 
     281 
     282      # CONNECTION MANAGEMENT ==================================== 
     283 
     284      def active? 
     285          retvalue = true 
     286          begin 
     287            retvalue = true if @connection.status == 1 
     288          rescue => e 
     289            retvalue = false 
     290          end 
     291          retvalue 
     292      end 
     293 
     294      def reconnect! 
     295        @connection.close rescue nil 
     296      
     297        @connection = FBSQL_Connect.connect(@connection_options[0], 
     298        @connection_options[1], 
     299        @connection_options[2], 
     300        @connection_options[3], 
     301        @connection_options[4], 
     302        @connection_options[5], 
     303        @connection_options[6])         
     304      end 
     305 
     306      # Close this connection 
     307      def disconnect! 
     308        @connection.close rescue nil 
     309        @active = false 
     310      end 
     311 
     312      # DATABASE STATEMENTS ====================================== 
     313 
     314      # Returns an array of record hashes with the column names as keys and 
     315      # column values as values. 
     316      def select_all(sql, name = nil) #:nodoc: 
     317        fbsql = cleanup_fb_sql(sql) 
     318        retValue = [] 
     319        fbresult = execute(sql, name) 
     320#         puts "select_all SQL -> #{fbsql}" 
     321        columns = fbresult.columns 
     322        fbresult.each do |row| 
     323#           puts "SQL <- #{row.inspect}" 
     324          hashed_row = {}     
     325          colnum = 0 
     326          row.each do |col| 
     327            hashed_row[columns[colnum]] = col 
     328            colnum += 1 
     329          end 
     330          retValue << hashed_row 
     331        end 
     332        retValue 
     333      end 
     334 
     335      def select_one(sql, name = nil) #:nodoc: 
     336        fbsql = cleanup_fb_sql(sql) 
     337        retValue = [] 
     338        fbresult = execute(fbsql, name) 
     339#         puts "SQL -> #{fbsql}" 
     340        columns = fbresult.columns 
     341         
     342        fbresult.each do |row| 
     343#           puts "SQL <- #{row.inspect}" 
     344          hashed_row = {}     
     345          colnum = 0 
     346          row.each do |col| 
     347            hashed_row[columns[colnum]] = col 
     348            colnum += 1 
     349          end 
     350          retValue << hashed_row 
     351          break 
     352        end 
     353        fbresult.clear 
     354        retValue = retValue.first 
     355      end 
     356 
     357      def query(sql, name = nil) #:nodoc: 
     358        fbsql = cleanup_fb_sql(sql) 
     359#         puts "SQL(query) -> #{fbsql}" 
     360        log(fbsql, name) { @connection.query(fbsql) } 
     361      rescue => e 
     362        puts "FB Exception: #{e.inspect}" 
     363        raise e 
     364      end 
     365 
     366      def execute(sql, name = nil) #:nodoc: 
     367        fbsql = cleanup_fb_sql(sql) 
     368#         puts "SQL(execute) -> #{fbsql}" 
     369        log(fbsql, name) { @connection.query(fbsql) } 
     370      rescue ActiveRecord::StatementInvalid => e 
     371        a = e.message.scan(/Table name - \w* - exists/) 
     372        if a.length == 0 
     373          raise e 
     374        end 
     375      end 
     376       
     377      # Returns the last auto-generated ID from the affected table. 
     378      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
     379#         puts "SQL -> #{sql.inspect}" 
     380        result = execute(sql, name) 
     381        id_value || pk 
     382      end 
     383 
     384      # Executes the update statement and returns the number of rows affected. 
     385      def update(sql, name = nil) #:nodoc: 
     386#         puts "SQL -> #{sql.inspect}" 
     387        execute(sql, name).num_rows 
     388      end 
     389 
     390      alias_method :delete, :update #:nodoc: 
     391 
     392      def set_pessimistic_transactions 
     393        if @transaction_mode == :optimistic 
     394          execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE" 
     395          @transaction_mode = :pessimistic 
     396        end 
     397      end 
     398 
     399      def set_optimistic_transactions 
     400        if @transaction_mode == :pessimistic 
     401          execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" 
     402          @transaction_mode = :optimistic 
     403        end 
     404      end 
     405 
     406      def begin_db_transaction #:nodoc: 
     407        execute "SET COMMIT FALSE" 
     408      rescue 
     409      end 
     410 
     411      def commit_db_transaction #:nodoc: 
     412        execute "COMMIT" 
     413      ensure 
     414        execute "SET COMMIT TRUE" 
     415      end 
     416 
     417      def rollback_db_transaction #:nodoc: 
     418        execute "ROLLBACK" 
     419        ensure 
     420          execute "SET COMMIT TRUE" 
     421      end 
     422 
     423      def add_limit_offset!(sql, options) #:nodoc 
     424        if limit = options[:limit] 
     425          offset = options[:offset] || 0 
     426         
     427# Here is the full syntax FrontBase supports: 
     428# (from gclem@frontbase.com) 
     429#  
     430#       TOP <limit - unsigned integer> 
     431#       TOP ( <offset expr>, <limit expr>) 
     432         
     433          # "TOP 0" is not allowed, so we have 
     434          # to use a cheap trick. 
     435          if limit == 0 
     436            if sql =~ /WHERE/i 
     437              sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ') 
     438            elsif 
     439              sql =~ /ORDER\s+BY/i 
     440              sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY') 
     441            else 
     442              sql << 'WHERE 0 = 1' 
     443            end 
     444          else 
     445            if offset == 0 
     446              sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ") 
     447            else 
     448              sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ") 
     449          end 
     450        end 
     451           
     452        end 
     453      end 
     454 
     455      def prefetch_primary_key?(table_name = nil) 
     456        true 
     457      end 
     458 
     459      # Returns the next sequence value from a sequence generator. Not generally 
     460      # called directly; used by ActiveRecord to get the next primary key value 
     461      # when inserting a new database record (see #prefetch_primary_key?). 
     462      def next_sequence_value(sequence_name) 
     463        unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") 
     464        # The test cases cannot handle a zero primary key 
     465        if unique == 0 then unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") end 
     466        unique  
     467      end 
     468 
     469      def default_sequence_name(table, column) 
     470        table; 
     471      end 
     472 
     473      # Set the sequence to the max value of the table's column. 
     474      def reset_sequence!(table, column, sequence = nil) 
     475        klasses = classes_for_table_name(table) 
     476        klass = klasses.nil? ? nil : klasses[0] 
     477        pk = klass.primary_key if klass != nil 
     478        if pk && klass.columns_hash[pk].type == :integer 
     479          execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") 
     480        end 
     481      end 
     482 
     483      def classes_for_table_name(table) 
     484        klasses = [] 
     485        # !!! Hack because subclasses is a protected method... 
     486        subclasses = ActiveRecord::Base.class_eval('@@subclasses') 
     487        subclasses.values.each do |a| 
     488          a.each {|n| klasses << n} 
     489        end 
     490        subset_klasses = klasses.select {|k| k.table_name == table} 
     491      end 
     492       
     493      def reset_pk_sequence!(table, pk = nil, sequence = nil) 
     494        klasses = classes_for_table_name(table) 
     495        klass = klasses.nil? ? nil : klasses[0] 
     496        pk = klass.primary_key if klass != nil 
     497        if pk && klass.columns_hash[pk].type == :integer 
     498          mpk = select_value("SELECT MAX(#{pk}) FROM #{table}") 
     499          execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") 
     500        end 
     501         
     502      end 
     503 
     504      # SCHEMA STATEMENTS ======================================== 
     505 
     506      def structure_dump #:nodoc: 
     507        select_all("SHOW TABLES").inject("") do |structure, table| 
     508          structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n" 
     509        end 
     510      end 
     511 
     512      def recreate_database(name) #:nodoc: 
     513        drop_database(name) 
     514        create_database(name) 
     515      end 
     516 
     517      def create_database(name) #:nodoc: 
     518        execute "CREATE DATABASE #{name}" 
     519      end 
     520       
     521      def drop_database(name) #:nodoc: 
     522        puts "TRACE: drop_database" 
     523        execute "DROP DATABASE #{name}" 
     524      end 
     525 
     526      def current_database 
     527        select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase 
     528      end 
     529 
     530      def tables(name = nil) #:nodoc: 
     531        sql = "SELECT \"TABLE_NAME\" FROM INFORMATION_SCHEMA.TABLES AS T0,INFORMATION_SCHEMA.SCHEMATA AS T1 WHERE T0.SCHEMA_PK = T1.SCHEMA_PK AND \"SCHEMA_NAME\" = CURRENT_SCHEMA" 
     532         
     533        select_values(sql,nil) 
     534      end 
     535 
     536      def indexes(table_name, name = nil)#:nodoc: 
     537        indexes = [] 
     538        current_index = nil 
     539        sql = "SELECT INDEX_NAME, T2.ORDINAL_POSITION ,INDEX_COLUMN_COUNT, INDEX_TYPE, \"COLUMN_NAME\", IS_NULLABLE FROM INFORMATION_SCHEMA.TABLES AS T0, INFORMATION_SCHEMA.INDEXES AS T1, INFORMATION_SCHEMA.INDEX_COLUMN_USAGE AS T2, INFORMATION_SCHEMA.COLUMNS AS T3 WHERE T0.\"TABLE_NAME\" = \'#{table_name}\' AND INDEX_TYPE <> 0 AND T0.TABLE_PK = T1.TABLE_PK AND T0.TABLE_PK = T2.TABLE_PK AND T0.TABLE_PK = T3.TABLE_PK AND T1. INDEXES_PK = T2.INDEX_PK AND T2.COLUMN_PK = T3.COLUMN_PK ORDER BY INDEX_NAME, T2.ORDINAL_POSITION" 
     540 
     541        columns = [] 
     542        query(sql).each do |row| 
     543          index_name   = row[0] 
     544          ord_position = row[1] 
     545          ndx_colcount = row[2] 
     546          index_type   = row[3] 
     547          column_name  = row[4] 
     548           
     549          if index_type == 1 
     550            isUnique = true 
     551          else 
     552            isUnique = false 
     553          end 
     554           
     555          columns << column_name 
     556          if ord_position == ndx_colcount 
     557            indexes << IndexDefinition.new(table_name, index_name, isUnique , columns) 
     558            columns = [] 
     559          end 
     560        end 
     561        indexes 
     562      end 
     563 
     564      def columns(table_name, name = nil)#:nodoc: 
     565        sql = "SELECT \"TABLE_NAME\", \"COLUMN_NAME\", ORDINAL_POSITION, IS_NULLABLE, COLUMN_DEFAULT, DATA_TYPE, DATA_TYPE_CODE, CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, DATETIME_PRECISION_LEADING FROM INFORMATION_SCHEMA.TABLES T0, INFORMATION_SCHEMA.COLUMNS T1, INFORMATION_SCHEMA.DATA_TYPE_DESCRIPTOR T3 WHERE \"TABLE_NAME\" = '#{table_name}' AND T0.TABLE_PK = T1.TABLE_PK AND T0.TABLE_PK = T3.TABLE_OR_DOMAIN_PK AND T1.COLUMN_PK = T3.COLUMN_NAME_PK ORDER BY T1.ORDINAL_POSITION" 
     566        rawresults = query(sql,name) 
     567        columns = [] 
     568        rawresults.each do |field| 
     569          typestring = field[5] 
     570          typecode = field[6] 
     571          base = field[0] 
     572          name = field[1] 
     573          limit = field[7] 
     574          precision = field[8] 
     575          scale = field[9] 
     576          default = field[4] 
     577          nullable = field[3] 
     578          columns << FrontBaseColumn.new(base, name, typecode, typestring, limit, precision, scale, default, nullable) 
     579         end 
     580        columns 
     581      end 
     582       
     583      def create_table(name, options = {}) 
     584        table_definition = TableDefinition.new(self) 
     585        table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false 
     586 
     587        yield table_definition 
     588 
     589        if options[:force] 
     590          drop_table(name) rescue nil 
     591        end 
     592 
     593        create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " 
     594        create_sql << "#{name} (" 
     595        create_sql << table_definition.to_sql 
     596        create_sql << ") #{options[:options]}" 
     597        begin_db_transaction 
     598        execute create_sql 
     599        commit_db_transaction 
     600        rescue ActiveRecord::StatementInvalid => e 
     601          a = e.message.scan(/Table name - \w* - exists/) 
     602          raise e if a.length == 0 
     603      end 
     604       
     605      def rename_table(name, new_name) 
     606        columns = columns(name) 
     607        pkcol = columns.find {|c| c.fb_autogen} 
     608        execute "ALTER TABLE NAME #{name} TO #{new_name}" 
     609        if pkcol 
     610          change_column_default(new_name,pkcol.name,"UNIQUE") 
     611          begin_db_transaction 
     612          mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}") 
     613          mpk = 0 if mpk.nil? 
     614          execute "SET UNIQUE=#{mpk} FOR #{new_name}" 
     615          commit_db_transaction 
     616        end 
     617      end   
     618 
     619      # Drops a table from the database. 
     620      def drop_table(name) 
     621        execute "DROP TABLE #{name} RESTRICT" 
     622      rescue ActiveRecord::StatementInvalid => e 
     623        a = e.message.scan(/Referenced TABLE - \w* - does not exist/) 
     624        raise e if a.length == 0 
     625      end 
     626 
     627      # Adds a new column to the named table. 
     628      # See TableDefinition#column for details of the options you can use. 
     629      def add_column(table_name, column_name, type, options = {}) 
     630        add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}" 
     631        options[:type] = type 
     632        add_column_options!(add_column_sql, options) 
     633        execute(add_column_sql) 
     634      end 
     635 
     636      def add_column_options!(sql, options) #:nodoc: 
     637        default_value = quote(options[:default], options[:column]) 
     638        if options[:default] 
     639          if options[:type] == :boolean 
     640            default_value = options[:default] == 0 ? quoted_false : quoted_true 
     641          end 
     642        end 
     643        sql << " DEFAULT #{default_value}" unless options[:default].nil? 
     644        sql << " NOT NULL" if options[:null] == false 
     645      end 
     646 
     647      # Removes the column from the table definition. 
     648      # ===== Examples 
     649      #  remove_column(:suppliers, :qualification) 
     650      def remove_column(table_name, column_name) 
     651        execute "ALTER TABLE #{table_name} DROP #{column_name} RESTRICT" 
     652      end 
     653 
     654      def remove_index(table_name, options = {}) #:nodoc: 
     655        execute "DROP INDEX #{index_name(table_name, options)}" 
     656      end 
     657 
     658      def change_column_default(table_name, column_name, default) #:nodoc: 
     659        execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL" 
     660      end 
     661 
     662      def change_column(table_name, column_name, type, options = {}) #:nodoc: 
     663        change_column_sql = "ALTER COLUMN \"#{table_name}\".\"#{column_name}\" TO #{type_to_sql(type, options[:limit])}" 
     664        execute(change_column_sql) 
     665        change_column_sql = "ALTER TABLE \"#{table_name}\" ALTER COLUMN \"#{column_name}\"" 
     666 
     667        default_value = quote(options[:default], options[:column]) 
     668        if options[:default] 
     669          if type == :boolean 
     670            default_value = options[:default] == 0 ? quoted_false : quoted_true 
     671          end 
     672        end 
     673 
     674        if default_value != "NULL" 
     675          change_column_sql << " SET DEFAULT #{default_value}" 
     676          execute(change_column_sql) 
     677        end 
     678         
     679#         change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}" 
     680#         add_column_options!(change_column_sql, options) 
     681#         execute(change_column_sql) 
     682      end 
     683 
     684      def rename_column(table_name, column_name, new_column_name) #:nodoc: 
     685        execute "ALTER COLUMN NAME \"#{table_name}\".\"#{column_name}\" TO \"#{new_column_name}\"" 
     686      end 
     687             
     688      private 
     689       
     690      # Clean up sql to make it something FrontBase can digest 
     691      def cleanup_fb_sql(sql) #:nodoc: 
     692        cleansql = sql.gsub("!=","<>") # Turn non-standard != into standard <> 
     693        lines = cleansql.split("\n") 
     694        lines = lines.select { |l| l.length > 0} # Strip blank lines 
     695        lines = lines.select { |l| !(l =~ /^--/)} # Strip comments 
     696        cleansql = lines.join("\n") 
     697        cleansql 
     698      end 
     699    end 
     700  end 
     701end 
  • activerecord/lib/active_record/fixtures.rb

    old new  
    391391    columns.join(", ") 
    392392  end 
    393393 
     394## Original 
     395#   def value_list 
     396#     @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ") 
     397#   end 
     398 
    394399  def value_list 
    395     @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ") 
     400    list = [] 
     401 
     402    # !!! This sometimes fails because the class cannot be found... 
     403    begin 
     404      klass = eval(@class_name) 
     405    rescue => e 
     406      klass = nil 
     407    end 
     408    @fixture.each_pair do |key, value| 
     409      col = klass.nil? ? nil : klass.columns_hash[key] 
     410      element = ActiveRecord::Base.connection.quote(value,col).gsub('\\n', "\n").gsub('\\r', "\r") 
     411      list << element 
     412    end 
     413    list = list.join(", ") 
     414    list 
    396415  end 
    397416 
    398417  def find 
  • activerecord/lib/active_record.rb

    old new  
    6868end 
    6969 
    7070unless defined?(RAILS_CONNECTION_ADAPTERS) 
    71   RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase
     71  RAILS_CONNECTION_ADAPTERS = %w( mysql postgresql sqlite firebird sqlserver db2 oracle sybase openbase frontbase
    7272end 
    7373 
    7474RAILS_CONNECTION_ADAPTERS.each do |adapter| 
  • activerecord/test/adapter_test.rb

    old new  
    6666  if ActiveRecord::Base.connection.respond_to?(:reset_pk_sequence!) 
    6767    require 'fixtures/movie' 
    6868    require 'fixtures/subscriber' 
     69     
    6970    def test_reset_empty_table_with_custom_pk 
    7071      Movie.delete_all 
    7172      Movie.connection.reset_pk_sequence! 'movies' 
    7273      assert_equal 1, Movie.create(:name => 'fight club').id 
    7374    end 
    7475 
    75     def test_reset_table_with_non_integer_pk 
    76       Subscriber.delete_all 
    77       Subscriber.connection.reset_pk_sequence! 'subscribers' 
    78  
    79       sub = Subscriber.new(:name => 'robert drake') 
    80       sub.id = 'bob drake' 
    81       assert_nothing_raised { sub.save! } 
     76    if ActiveRecord::Base.connection.adapter_name != "FrontBase" 
     77      def test_reset_table_with_non_integer_pk 
     78        Subscriber.delete_all 
     79        Subscriber.connection.reset_pk_sequence! 'subscribers' 
     80        sub = Subscriber.new(:name => 'robert drake') 
     81        sub.id = 'bob drake' 
     82        assert_nothing_raised { sub.save! } 
     83      end 
    8284    end 
    8385  end 
    8486 
  • activerecord/test/base_test.rb

    old new  
    10941094    end 
    10951095    assert_equal res, res3 
    10961096     
    1097     res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id" 
     1097    res4 = Post.count_by_sql "SELECT COUNT(p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" 
    10981098    res5 = nil 
    10991099    assert_nothing_raised do 
    1100       res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id", 
    1101                         :joins => "p, comments c", 
     1100      res5 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", 
     1101                        :joins => "p, comments co", 
    11021102                        :select => "p.id") 
    11031103    end 
    11041104 
    11051105    assert_equal res4, res5  
    11061106 
    1107     res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments c WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id" 
     1107    res6 = Post.count_by_sql "SELECT COUNT(DISTINCT p.id) FROM posts p, comments co WHERE p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id" 
    11081108    res7 = nil 
    11091109    assert_nothing_raised do 
    1110       res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=c.post_id", 
    1111                         :joins => "p, comments c", 
     1110      res7 = Post.count(:conditions => "p.#{QUOTED_TYPE} = 'Post' AND p.id=co.post_id", 
     1111                        :joins => "p, comments co", 
    11121112                        :select => "p.id", 
    11131113                        :distinct => true) 
    11141114    end 
  • activerecord/test/connections/native_frontbase/connection.rb

    old new  
     1print "Using native Frontbase\n" 
     2require_dependency 'fixtures/course' 
     3require 'logger' 
     4 
     5ActiveRecord::Base.logger = Logger.new("debug.log") 
     6 
     7db1 = 'activerecord_unittest' 
     8db2 = 'activerecord_unittest2' 
     9 
     10ActiveRecord::Base.establish_connection( 
     11  :adapter      => "frontbase", 
     12  :host         => "localhost", 
     13  :username     => "rails", 
     14  :password     => "", 
     15  :database     => db1, 
     16  :session_name => "unittest-#{$$}" 
     17) 
     18 
     19Course.establish_connection( 
     20  :adapter      => "frontbase", 
     21  :host         => "localhost", 
     22  :username     => "rails", 
     23  :password     => "", 
     24  :database     => db2, 
     25  :session_name => "unittest-#{$$}" 
     26) 
  • activerecord/test/deprecated_finder_test.rb

    old new  
    11require 'abstract_unit' 
    22require 'fixtures/company' 
    33require 'fixtures/topic' 
     4require 'fixtures/reply' 
    45require 'fixtures/entrant' 
    56require 'fixtures/developer' 
    67 
  • activerecord/test/fixtures/db_definitions/frontbase.drop.sql

    old new  
     1DROP TABLE accounts CASCADE; 
     2DROP TABLE funny_jokes CASCADE; 
     3DROP TABLE companies CASCADE; 
     4DROP TABLE topics CASCADE; 
     5DROP TABLE developers CASCADE; 
     6DROP TABLE projects CASCADE; 
     7DROP TABLE developers_projects CASCADE; 
     8DROP TABLE orders CASCADE; 
     9DROP TABLE customers CASCADE; 
     10DROP TABLE movies CASCADE; 
     11DROP TABLE subscribers CASCADE; 
     12DROP TABLE booleantests CASCADE; 
     13DROP TABLE auto_id_tests CASCADE; 
     14DROP TABLE entrants CASCADE; 
     15DROP TABLE colnametests CASCADE; 
     16DROP TABLE mixins CASCADE; 
     17DROP TABLE people CASCADE; 
     18DROP TABLE readers CASCADE; 
     19DROP TABLE binaries CASCADE; 
     20DROP TABLE computers CASCADE; 
     21DROP TABLE posts CASCADE; 
     22DROP TABLE comments CASCADE; 
     23DROP TABLE authors CASCADE; 
     24DROP TABLE tasks CASCADE; 
     25DROP TABLE categories CASCADE; 
     26DROP TABLE categories_posts CASCADE; 
     27DROP TABLE fk_test_has_fk CASCADE; 
     28DROP TABLE fk_test_has_pk CASCADE; 
     29DROP TABLE keyboards CASCADE; 
     30DROP TABLE legacy_things CASCADE; 
  • activerecord/test/fixtures/db_definitions/frontbase.sql

    old new  
     1CREATE TABLE accounts ( 
     2    id integer DEFAULT unique, 
     3    firm_id integer, 
     4    credit_limit integer, 
     5    PRIMARY KEY (id) 
     6); 
     7SET UNIQUE FOR accounts(id); 
     8 
     9CREATE TABLE funny_jokes ( 
     10  id integer DEFAULT unique, 
     11  firm_id integer default NULL, 
     12  name character varying(50), 
     13  PRIMARY KEY (id) 
     14); 
     15SET UNIQUE FOR funny_jokes(id); 
     16   
     17CREATE TABLE companies ( 
     18  id integer DEFAULT unique, 
     19    "type" character varying(50), 
     20    "ruby_type" character varying(50), 
     21    firm_id integer, 
     22    name character varying(50), 
     23    client_of integer, 
     24    rating integer default 1, 
     25    PRIMARY KEY (id) 
     26); 
     27SET UNIQUE FOR companies(id); 
     28 
     29CREATE TABLE topics ( 
     30  id integer DEFAULT unique, 
     31    title character varying(255), 
     32    author_name character varying(255), 
     33    author_email_address character varying(255), 
     34    written_on timestamp, 
     35    bonus_time time, 
     36    last_read date, 
     37    content varchar(65536), 
     38    approved boolean default true, 
     39    replies_count integer default 0, 
     40    parent_id integer, 
     41    "type" character varying(50), 
     42    PRIMARY KEY (id) 
     43); 
     44SET UNIQUE FOR topics(id); 
     45 
     46CREATE TABLE developers ( 
     47  id integer DEFAULT unique, 
     48    name character varying(100), 
     49    salary integer DEFAULT 70000, 
     50    created_at timestamp, 
     51    updated_at timestamp, 
     52    PRIMARY KEY (id) 
     53); 
     54SET UNIQUE FOR developers(id); 
     55 
     56CREATE TABLE projects ( 
     57  id integer DEFAULT unique, 
     58    name character varying(100), 
     59    type varchar(255), 
     60    PRIMARY KEY (id) 
     61); 
     62SET UNIQUE FOR projects(id); 
     63 
     64CREATE TABLE developers_projects ( 
     65    developer_id integer NOT NULL, 
     66    project_id integer NOT NULL, 
     67    joined_on date, 
     68    access_level integer default 1 
     69); 
     70 
     71CREATE TABLE orders ( 
     72  id integer DEFAULT unique, 
     73    name character varying(100), 
     74    billing_customer_id integer, 
     75    shipping_customer_id integer, 
     76    PRIMARY KEY (id) 
     77); 
     78SET UNIQUE FOR orders(id); 
     79 
     80CREATE TABLE customers ( 
     81  id integer DEFAULT unique, 
     82    name character varying(100), 
     83    balance integer default 0, 
     84    address_street character varying(100), 
     85    address_city character varying(100), 
     86    address_country character varying(100), 
     87    gps_location character varying(100), 
     88    PRIMARY KEY (id) 
     89); 
     90SET UNIQUE FOR customers(id); 
     91 
     92CREATE TABLE movies ( 
     93    movieid integer DEFAULT unique, 
     94    name varchar(65536), 
     95    PRIMARY KEY (movieid) 
     96); 
     97SET UNIQUE FOR movies(movieid); 
     98 
     99CREATE TABLE subscribers ( 
     100    nick varchar(65536) NOT NULL, 
     101    name varchar(65536), 
     102    PRIMARY KEY (nick) 
     103); 
     104 
     105CREATE TABLE booleantests ( 
     106  id integer DEFAULT unique, 
     107    value boolean, 
     108    PRIMARY KEY (id) 
     109); 
     110SET UNIQUE FOR booleantests(id); 
     111 
     112CREATE TABLE auto_id_tests ( 
     113  auto_id integer DEFAULT unique, 
     114    value integer, 
     115    PRIMARY KEY (auto_id) 
     116); 
     117SET UNIQUE FOR auto_id_tests(auto_id); 
     118 
     119CREATE TABLE entrants ( 
     120  id integer DEFAULT unique, 
     121  name varchar(65536), 
     122  course_id integer, 
     123  PRIMARY KEY (id) 
     124); 
     125SET UNIQUE FOR entrants(id); 
     126 
     127CREATE TABLE colnametests ( 
     128  id integer DEFAULT unique, 
     129  "references" integer NOT NULL, 
     130  PRIMARY KEY (id) 
     131); 
     132SET UNIQUE FOR colnametests(id); 
     133 
     134CREATE TABLE mixins ( 
     135  id integer DEFAULT unique, 
     136  parent_id integer, 
     137  type character varying(100),   
     138  pos integer, 
     139  lft integer, 
     140  rgt integer, 
     141  root_id integer,   
     142  created_at timestamp, 
     143  updated_at timestamp, 
     144  PRIMARY KEY (id)