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

Ticket #4093: FrontBaseAdapter.5.patch

File FrontBaseAdapter.5.patch, 48.8 kB (added by mlaster@metavillage.com, 2 years ago)

Latest patch as of revision 4241

  • 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/base.rb

    old new  
    755755        superclass == Base || !columns_hash.include?(inheritance_column) 
    756756      end 
    757757 
    758       def quote(object) #:nodoc: 
    759         connection.quote(object) 
     758      def quote(value, column = nil) 
     759       #:nodoc: 
     760        connection.quote(value,column) 
    760761      end 
    761762 
    762763      # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>. 
     
    947948       
    948949        def find_one(id, options) 
    949950          conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] 
    950           options.update :conditions => "#{table_name}.#{primary_key} = #{sanitize(id)}#{conditions}" 
     951          options.update :conditions => "#{table_name}.#{primary_key} = #{quote(id,columns_hash[primary_key])}#{conditions}" 
    951952 
    952953          if result = find_initial(options) 
    953954            result 
     
    958959       
    959960        def find_some(ids, options) 
    960961          conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions] 
    961           ids_list   = ids.map { |id| sanitize(id) }.join(',') 
     962          ids_list   = ids.map { |id| quote(id,columns_hash[primary_key]) }.join(',') 
    962963          options.update :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}" 
    963964 
    964965          result = find_every(options) 
  • activerecord/lib/active_record/connection_adapters/frontbase_adapter.rb

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

    old new  
    1313    assert_equal(topics(:first).title, Topic.find(1).title) 
    1414  end 
    1515   
     16  # find should handle strings that come from URLs 
     17  # (example: Category.find(params[:id])) 
     18  def test_find_with_string 
     19    assert_equal(Topic.find(1).title,Topic.find("1").title) 
     20  end 
     21   
    1622  def test_exists 
    1723    assert (Topic.exists?(1)) 
    1824    assert !(Topic.exists?(45)) 
  • 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