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

Ticket #4093: patch_against_4245.patch

File patch_against_4245.patch, 53.3 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    # From EOF Documentation.... 
     63    # buffer should have space for EOUniqueBinaryKeyLength (12) bytes. 
     64    # Assigns a world-wide unique ID made up of: 
     65    # < Sequence [2], ProcessID [2] , Time [4], IP Addr [4] > 
     66     
     67    class TwelveByteKey < String #:nodoc 
     68      @@mutex = Mutex.new 
     69      @@sequence_number = rand(65536) 
     70      @@key_cached_pid_component = nil 
     71      @@key_cached_ip_component = nil 
     72 
     73      def initialize(str=nil) 
     74        if !str.nil? && str.length != 12 
     75          raise "str is not 12 bytes long!" if str.length != 12 
     76        end 
     77         
     78        # Generate a unique key 
     79        if str.nil? 
     80          new_key = self.replace("____________") 
     81 
     82          new_key[0..1] = self.class.key_sequence_component         
     83          new_key[2..3] = self.class.key_pid_component 
     84          new_key[4..7] = self.class.key_time_component 
     85          new_key[8..11] = self.class.key_ip_component 
     86          new_key 
     87        else 
     88          super(str) 
     89        end 
     90      end 
     91             
     92      def inspect 
     93        s = self.unpack("H*")[0] 
     94        "#{s}".upcase 
     95      end 
     96     
     97      alias to_s inspect 
     98       
     99      private 
     100             
     101      def self.key_sequence_component 
     102        seq = nil 
     103        @@mutex.synchronize do 
     104          seq = @@sequence_number 
     105          @@sequence_number = (@@sequence_number + 1) % 65536 
     106        end 
     107         
     108        sequence_component = "__" 
     109        sequence_component[0] = seq >> 8 
     110        sequence_component[1] = seq 
     111        sequence_component 
     112      end 
     113       
     114      def self.key_pid_component 
     115        if @@key_cached_pid_component.nil? 
     116          @@mutex.synchronize do 
     117            pid = $$ 
     118            pid_component = "__" 
     119            pid_component[0] = pid >> 8 
     120            pid_component[1] = pid 
     121            @@key_cached_pid_component = pid_component 
     122          end 
     123        end 
     124        @@key_cached_pid_component 
     125      end 
     126       
     127      def self.key_time_component 
     128        time = Time.new.to_i 
     129        time_component = "____" 
     130        time_component[0] = (time & 0xFF000000) >> 24 
     131        time_component[1] = (time & 0x00FF0000) >> 16 
     132        time_component[2] = (time & 0x0000FF00) >> 8 
     133        time_component[3] = (time & 0x000000FF) 
     134        time_component 
     135      end 
     136       
     137      def self.key_ip_component 
     138        if @@key_cached_ip_component.nil? 
     139          @@mutex.synchronize do 
     140            old_lookup_flag  = BasicSocket.do_not_reverse_lookup 
     141            BasicSocket.do_not_reverse_lookup = true 
     142            udpsocket = UDPSocket.new 
     143            udpsocket.connect("17.112.152.32",1) 
     144            ip_string = udpsocket.addr[3] 
     145            BasicSocket.do_not_reverse_lookup = old_lookup_flag 
     146            packed = Socket.pack_sockaddr_in(0,ip_string) 
     147            addr_subset = packed[4..7] 
     148            ip = addr_subset[0] << 24 | addr_subset[1] << 16 | addr_subset[2] << 8 | addr_subset[3] 
     149            ip_component = "____" 
     150            ip_component[0] = (ip & 0xFF000000) >> 24 
     151            ip_component[1] = (ip & 0x00FF0000) >> 16 
     152            ip_component[2] = (ip & 0x0000FF00) >> 8 
     153            ip_component[3] = (ip & 0x000000FF) 
     154            @@key_cached_ip_component = ip_component 
     155          end 
     156        end 
     157        @@key_cached_ip_component 
     158      end 
     159    end 
     160     
     161    class FrontBaseColumn < Column #:nodoc: 
     162      attr_reader :fb_autogen 
     163       
     164      def initialize(base, name, type, typename, limit, precision, scale, default, nullable) 
     165         
     166        @base, @name = base, name 
     167        @type = simplified_type(type,typename,limit) 
     168        @limit = limit 
     169        @precision, @scale = precision, scale 
     170        @default = default 
     171        @null = nullable == "YES" ? true : false 
     172        @text    = [:string, :text].include? @type 
     173        @number  = [:float, :integer].include? @type 
     174        @fb_autogen = false 
     175        if @default 
     176          @default.gsub!(/^'(.*)'$/,'\1') if @text 
     177          @fb_autogen =  @default.include?("SELECT UNIQUE FROM") 
     178          if @type == :boolean then 
     179            @default = (@default == "TRUE") 
     180          elsif @type == :binary 
     181            if @default != "X''" 
     182              buffer = "" 
     183              @default.scan(/../) { |h| buffer << h.hex.chr } 
     184              @default = buffer 
     185            else 
     186              @default = "" 
     187            end 
     188          else 
     189            @default = type_cast(@default) 
     190          end 
     191        end 
     192      end 
     193       
     194      # Casts value (which is a String) to an appropriate instance. 
     195      def type_cast(value) 
     196        if type == :twelvebytekey 
     197          ActiveRecord::ConnectionAdapters::TwelveByteKey.new(value) 
     198        else 
     199          super(value) 
     200        end 
     201      end 
     202 
     203      def type_cast_code(var_name) 
     204        if type == :twelvebytekey 
     205          "ActiveRecord::ConnectionAdapters::TwelveByteKey.new(#{var_name})" 
     206        else 
     207          super(var_name) 
     208        end 
     209      end 
     210 
     211      private 
     212      def simplified_type(field_type, type_name,limit) 
     213        ret_type = :string 
     214         puts "typecode: [#{field_type}] [#{type_name}]"  if FB_TRACE 
     215          
     216         # 12 byte primary keys are a special case that Apple's EOF 
     217         # used heavily.  Optimize for this case 
     218         if field_type == 11 && limit == 96 
     219           ret_type = :twelvebytekey            # BIT(96) 
     220         else 
     221           case field_type 
     222             when 1 then ret_type = :boolean    # BOOLEAN 
     223             when 2 then ret_type = :integer    # INTEGER 
     224             when 4 then ret_type = :float      # FLOAT 
     225             when 10 then ret_type = :string    # CHARACTER VARYING 
     226             when 11 then ret_type = :bitfield  # BIT 
     227             when 13 then ret_type = :date      # DATE 
     228             when 14 then ret_type = :time      # TIME 
     229             when 16 then ret_type = :timestamp # TIMESTAMP 
     230             when 20 then ret_type = :text      # CLOB 
     231             when 21 then ret_type = :binary    # BLOB 
     232             when 22 then ret_type = :integer   # TINYINT 
     233             else 
     234               puts "ERROR: Unknown typecode: [#{field_type}] [#{type_name}]" 
     235           end 
     236         end 
     237         puts "ret_type: #{ret_type.inspect}" if FB_TRACE 
     238        ret_type 
     239      end 
     240    end 
     241 
     242    class FrontBaseAdapter < AbstractAdapter 
     243             
     244      def initialize(connection, logger, connection_options, config) 
     245        super(connection, logger) 
     246        @connection_options, @config = connection_options, config 
     247        @transaction_mode = :pessimistic 
     248         
     249        # threaded_connections_test.rb will fail unless we set the session 
     250        # to optimistic locking mode 
     251#         set_pessimistic_transactions 
     252#         execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" 
     253      end 
     254 
     255      # Returns the human-readable name of the adapter.  Use mixed case - one 
     256      # can always use downcase if needed. 
     257      def adapter_name #:nodoc: 
     258        'FrontBase' 
     259      end 
     260 
     261      # Does this adapter support migrations?  Backend specific, as the 
     262      # abstract adapter always returns +false+. 
     263      def supports_migrations? #:nodoc: 
     264        true 
     265      end 
     266 
     267      def native_database_types #:nodoc 
     268        { 
     269          :primary_key    => "INTEGER DEFAULT UNIQUE PRIMARY KEY", 
     270          :string         => { :name => "VARCHAR", :limit => 255 }, 
     271          :text           => { :name => "CLOB" }, 
     272          :integer        => { :name => "INTEGER" }, 
     273          :float          => { :name => "FLOAT" }, 
     274          :datetime       => { :name => "TIMESTAMP" }, 
     275          :timestamp      => { :name => "TIMESTAMP" }, 
     276          :time           => { :name => "TIME" }, 
     277          :date           => { :name => "DATE" }, 
     278          :binary         => { :name => "BLOB" }, 
     279          :boolean        => { :name => "BOOLEAN" }, 
     280          :twelvebytekey  => { :name => "BYTE", :limit => 12} 
     281        } 
     282      end 
     283 
     284 
     285      # QUOTING ================================================== 
     286 
     287      # Quotes the column value to help prevent 
     288      # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. 
     289      def quote(value, column = nil) 
     290        retvalue = "<INVALID>" 
     291    
     292        puts "quote(#{value.inspect}(#{value.class}),#{column.type.inspect})" if FB_TRACE 
     293        # If a column was passed in, use column type information 
     294        if ! value.nil? 
     295          if column 
     296            case column.type 
     297              when :string 
     298                if value.kind_of?(String) 
     299                  retvalue = "'#{quote_string(value.to_s)}'" # ' (for ruby-mode) 
     300                else 
     301                  retvalue = "'#{quote_string(value.to_yaml)}'" 
     302                end 
     303              when :integer 
     304                if value.kind_of?(TrueClass) 
     305                  retvalue = '1' 
     306                elsif value.kind_of?(FalseClass) 
     307                  retvalue = '0' 
     308                else 
     309                  retvalue = value.to_i.to_s 
     310                end 
     311              when :float 
     312                retvalue = value.to_f.to_s 
     313              when :datetime, :timestamp 
     314                retvalue = "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" 
     315              when :time 
     316                retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     317              when :date 
     318                retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     319              when :twelvebytekey 
     320                s = value.to_s.unpack("H*")[0] 
     321                retvalue = "X'#{s}'" 
     322              when :boolean 
     323                retvalue = quoted_true if value.kind_of?(TrueClass) 
     324                retvalue = quoted_false if value.kind_of?(FalseClass) 
     325              when :binary 
     326                blob_handle = @connection.create_blob(value.to_s) 
     327                retvalue = blob_handle.handle 
     328                puts "SQL -> Insert #{value.to_s.length} byte blob as #{retvalue}" if FB_TRACE 
     329              when :text 
     330                if value.kind_of?(String) 
     331                  clobdata = value.to_s # ' (for ruby-mode) 
     332                else 
     333                  clobdata = value.to_yaml 
     334                end 
     335                clob_handle = @connection.create_clob(clobdata) 
     336                retvalue = clob_handle.handle 
     337                puts "SQL -> Insert #{value.to_s.length} byte clob as #{retvalue}" if FB_TRACE 
     338                 
     339              else 
     340                raise "*** UNKNOWN TYPE: #{column.type.inspect}" 
     341            end # case 
     342          # Since we don't have column type info, make a best guess based 
     343          # on the Ruby class of the value 
     344          else 
     345            case value 
     346              when String 
     347                if column && column.type == :binary 
     348                  s = value.unpack("H*")[0] 
     349                  retvalue = "X'#{s}'" 
     350                elsif column && [:integer, :float].include?(column.type)  
     351                  retvalue = value.to_s 
     352                else 
     353                  retvalue = "'#{quote_string(value)}'" # ' (for ruby-mode) 
     354                end 
     355              when NilClass              then retvalue = "NULL" 
     356              when TrueClass             then retvalue = (column && column.type == :integer ? '1' : quoted_true) 
     357              when FalseClass            then retvalue = (column && column.type == :integer ? '0' : quoted_false) 
     358              when Float, Fixnum, Bignum then retvalue = value.to_s 
     359              when Time, Date, DateTime 
     360                if column 
     361                  case column.type 
     362                    when :date then retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     363                    when :time then retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     364                    when :timestamp then retvalue = "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" 
     365                  else 
     366                    raise NotImplementedError, "Unknown column type!" 
     367                  end # case 
     368                else # Column wasn't passed in, so try to guess the right type 
     369                  if value.kind_of? Date 
     370                    retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     371                  else 
     372                    if value.hour == 0 && value.min == 0 && value.sec == 0 
     373                      retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     374                    else 
     375                      retvalue = "TIMESTAMP '#{quoted_date(value)}'" 
     376                    end 
     377                  end  
     378                end #if column 
     379              when ActiveRecord::ConnectionAdapters::TwelveByteKey 
     380                s = value.unpack("H*")[0] 
     381                retvalue = "X'#{s}'" 
     382              else "'#{quote_string(value.to_yaml)}'" 
     383            end #case 
     384          end 
     385        else 
     386          retvalue = "NULL" 
     387        end 
     388          
     389      retvalue 
     390      end # def 
     391 
     392      # Quotes a string, escaping any ' (single quote) characters. 
     393      def quote_string(s) 
     394        s.gsub(/'/, "''") # ' (for ruby-mode) 
     395      end 
     396 
     397      def quote_column_name(name) #:nodoc: 
     398        "\"#{name}\"" 
     399      end 
     400 
     401      def quoted_true 
     402        "true" 
     403      end 
     404       
     405      def quoted_false 
     406        "false" 
     407      end 
     408 
     409 
     410      # CONNECTION MANAGEMENT ==================================== 
     411 
     412      def active? 
     413          retvalue = true 
     414          begin 
     415            retvalue = true if @connection.status == 1 
     416          rescue => e 
     417            retvalue = false 
     418          end 
     419          retvalue 
     420      end 
     421 
     422      def reconnect! 
     423        @connection.close rescue nil 
     424      
     425        @connection = FBSQL_Connect.connect(@connection_options[0], 
     426        @connection_options[1], 
     427        @connection_options[2], 
     428        @connection_options[3], 
     429        @connection_options[4], 
     430        @connection_options[5], 
     431        @connection_options[6])         
     432      end 
     433 
     434      # Close this connection 
     435      def disconnect! 
     436        @connection.close rescue nil 
     437        @active = false 
     438      end 
     439 
     440      # DATABASE STATEMENTS ====================================== 
     441 
     442      # Returns an array of record hashes with the column names as keys and 
     443      # column values as values. 
     444      def select_all(sql, name = nil) #:nodoc: 
     445        fbsql = cleanup_fb_sql(sql) 
     446        retValue = [] 
     447        fbresult = execute(sql, name) 
     448         puts "select_all SQL -> #{fbsql}" if FB_TRACE 
     449        columns = fbresult.columns 
     450        fbresult.each do |row| 
     451          puts "SQL <- #{row.inspect}"  if FB_TRACE 
     452          hashed_row = {}     
     453          colnum = 0 
     454          row.each do |col| 
     455            hashed_row[columns[colnum]] = col 
     456            if col.kind_of?(FBSQL_LOB) 
     457              hashed_row[columns[colnum]] = col.read 
     458            end 
     459            colnum += 1 
     460          end 
     461          puts "raw row: #{hashed_row.inspect}" if FB_TRACE 
     462          retValue << hashed_row 
     463        end 
     464        retValue 
     465      end 
     466 
     467      def select_one(sql, name = nil) #:nodoc: 
     468        fbsql = cleanup_fb_sql(sql) 
     469        retValue = [] 
     470        fbresult = execute(fbsql, name) 
     471        puts "SQL -> #{fbsql}"  if FB_TRACE 
     472        columns = fbresult.columns 
     473         
     474        fbresult.each do |row| 
     475           puts "SQL <- #{row.inspect}"  if FB_TRACE 
     476          hashed_row = {}     
     477          colnum = 0 
     478          row.each do |col| 
     479            hashed_row[columns[colnum]] = col 
     480            if col.kind_of?(FBSQL_LOB) 
     481              hashed_row[columns[colnum]] = col.read 
     482            end 
     483            colnum += 1 
     484          end 
     485          retValue << hashed_row 
     486          break 
     487        end 
     488        fbresult.clear 
     489        retValue = retValue.first 
     490      end 
     491 
     492      def query(sql, name = nil) #:nodoc: 
     493        fbsql = cleanup_fb_sql(sql) 
     494        puts "SQL(query) -> #{fbsql}"  if FB_TRACE 
     495        log(fbsql, name) { @connection.query(fbsql) } 
     496      rescue => e 
     497        puts "FB Exception: #{e.inspect}" if FB_TRACE 
     498        raise e 
     499      end 
     500 
     501      def execute(sql, name = nil) #:nodoc: 
     502        fbsql = cleanup_fb_sql(sql) 
     503        puts "SQL(execute) -> #{fbsql}"  if FB_TRACE 
     504        log(fbsql, name) { @connection.query(fbsql) } 
     505      rescue ActiveRecord::StatementInvalid => e 
     506        a = e.message.scan(/Table name - \w* - exists/) 
     507        if a.length == 0 
     508          puts "FB Exception: #{e.inspect}" if FB_TRACE 
     509          raise e 
     510        end 
     511      end 
     512       
     513      # Returns the last auto-generated ID from the affected table. 
     514      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
     515        puts "SQL -> #{sql.inspect}"  if FB_TRACE 
     516        result = execute(sql, name) 
     517        id_value || pk 
     518      end 
     519 
     520      # Executes the update statement and returns the number of rows affected. 
     521      def update(sql, name = nil) #:nodoc: 
     522        puts "SQL -> #{sql.inspect}"  if FB_TRACE 
     523        execute(sql, name).num_rows 
     524      end 
     525 
     526      alias_method :delete, :update #:nodoc: 
     527 
     528      def set_pessimistic_transactions 
     529        if @transaction_mode == :optimistic 
     530          execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE" 
     531          @transaction_mode = :pessimistic 
     532        end 
     533      end 
     534 
     535      def set_optimistic_transactions 
     536        if @transaction_mode == :pessimistic 
     537          execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" 
     538          @transaction_mode = :optimistic 
     539        end 
     540      end 
     541 
     542      def begin_db_transaction #:nodoc: 
     543        execute "SET COMMIT FALSE" 
     544      rescue 
     545      end 
     546 
     547      def commit_db_transaction #:nodoc: 
     548        execute "COMMIT" 
     549      ensure 
     550        execute "SET COMMIT TRUE" 
     551      end 
     552 
     553      def rollback_db_transaction #:nodoc: 
     554        execute "ROLLBACK" 
     555        ensure 
     556          execute "SET COMMIT TRUE" 
     557      end 
     558 
     559      def add_limit_offset!(sql, options) #:nodoc 
     560        if limit = options[:limit] 
     561          offset = options[:offset] || 0 
     562         
     563# Here is the full syntax FrontBase supports: 
     564# (from gclem@frontbase.com) 
     565#  
     566#       TOP <limit - unsigned integer> 
     567#       TOP ( <offset expr>, <limit expr>) 
     568         
     569          # "TOP 0" is not allowed, so we have 
     570          # to use a cheap trick. 
     571          if limit == 0 
     572            if sql =~ /WHERE/i 
     573              sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ') 
     574            elsif 
     575              sql =~ /ORDER\s+BY/i 
     576              sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY') 
     577            else 
     578              sql << 'WHERE 0 = 1' 
     579            end 
     580          else 
     581            if offset == 0 
     582              sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ") 
     583            else 
     584              sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ") 
     585          end 
     586        end 
     587           
     588        end 
     589      end 
     590 
     591      def prefetch_primary_key?(table_name = nil) 
     592        true 
     593      end 
     594 
     595      # Returns the next sequence value from a sequence generator. Not generally 
     596      # called directly; used by ActiveRecord to get the next primary key value 
     597      # when inserting a new database record (see #prefetch_primary_key?). 
     598      def next_sequence_value(sequence_name) 
     599        unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") 
     600        # The test cases cannot handle a zero primary key 
     601        if unique == 0 then unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") end 
     602        unique  
     603      end 
     604 
     605      def default_sequence_name(table, column) 
     606        table; 
     607      end 
     608 
     609      # Set the sequence to the max value of the table's column. 
     610      def reset_sequence!(table, column, sequence = nil) 
     611        klasses = classes_for_table_name(table) 
     612        klass = klasses.nil? ? nil : klasses[0] 
     613        pk = klass.primary_key if !klass.nil? 
     614        if pk && klass.columns_hash[pk].type == :integer 
     615          execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") 
     616        end 
     617      end 
     618 
     619      def classes_for_table_name(table) 
     620        klasses = [] 
     621        # !!! Hack because subclasses is a protected method... 
     622        subclasses = ActiveRecord::Base.class_eval('@@subclasses') 
     623        subclasses.values.each do |a| 
     624          a.each {|n| klasses << n} 
     625        end 
     626        subset_klasses = klasses.select {|k| k.table_name == table} 
     627      end 
     628       
     629      def reset_pk_sequence!(table, pk = nil, sequence = nil) 
     630        klasses = classes_for_table_name(table) 
     631        klass = klasses.nil? ? nil : klasses[0] 
     632        pk = klass.primary_key if !klass.nil? 
     633        if pk && klass.columns_hash[pk].type == :integer 
     634          mpk = select_value("SELECT MAX(#{pk}) FROM #{table}") 
     635          execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") 
     636        end 
     637         
     638      end 
     639 
     640      # SCHEMA STATEMENTS ======================================== 
     641 
     642      def structure_dump #:nodoc: 
     643        select_all("SHOW TABLES").inject("") do |structure, table| 
     644          structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n" 
     645        end 
     646      end 
     647 
     648      def recreate_database(name) #:nodoc: 
     649        drop_database(name) 
     650        create_database(name) 
     651      end 
     652 
     653      def create_database(name) #:nodoc: 
     654        execute "CREATE DATABASE #{name}" 
     655      end 
     656       
     657      def drop_database(name) #:nodoc: 
     658        execute "DROP DATABASE #{name}" 
     659      end 
     660 
     661      def current_database 
     662        select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase 
     663      end 
     664 
     665      def tables(name = nil) #:nodoc: 
     666        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" 
     667         
     668        select_values(sql,nil) 
     669      end 
     670 
     671      def indexes(table_name, name = nil)#:nodoc: 
     672        indexes = [] 
     673        current_index = nil 
     674        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" 
     675 
     676        columns = [] 
     677        query(sql).each do |row| 
     678          index_name   = row[0] 
     679          ord_position = row[1] 
     680          ndx_colcount = row[2] 
     681          index_type   = row[3] 
     682          column_name  = row[4] 
     683           
     684          if index_type == 1 
     685            isUnique = true 
     686          else 
     687            isUnique = false 
     688          end 
     689           
     690          columns << column_name 
     691          if ord_position == ndx_colcount 
     692            indexes << IndexDefinition.new(table_name, index_name, isUnique , columns) 
     693            columns = [] 
     694          end 
     695        end 
     696        indexes 
     697      end 
     698 
     699      def columns(table_name, name = nil)#:nodoc: 
     700        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" 
     701        rawresults = query(sql,name) 
     702        columns = [] 
     703        rawresults.each do |field| 
     704          typestring = field[5] 
     705          typecode = field[6] 
     706          base = field[0] 
     707          name = field[1] 
     708          limit = field[7] 
     709          precision = field[8] 
     710          scale = field[9] 
     711          default = field[4] 
     712          nullable = field[3] 
     713          columns << FrontBaseColumn.new(base, name, typecode, typestring, limit, precision, scale, default, nullable) 
     714         end 
     715        columns 
     716      end 
     717       
     718      def create_table(name, options = {}) 
     719        table_definition = TableDefinition.new(self) 
     720        table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false 
     721 
     722        yield table_definition 
     723 
     724        if options[:force] 
     725          drop_table(name) rescue nil 
     726        end 
     727 
     728        create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " 
     729        create_sql << "#{name} (" 
     730        create_sql << table_definition.to_sql 
     731        create_sql << ") #{options[:options]}" 
     732        begin_db_transaction 
     733        execute create_sql 
     734        commit_db_transaction 
     735        rescue ActiveRecord::StatementInvalid => e 
     736          a = e.message.scan(/Table name - \w* - exists/) 
     737          raise e if a.length == 0 
     738      end 
     739       
     740      def rename_table(name, new_name) 
     741        columns = columns(name) 
     742        pkcol = columns.find {|c| c.fb_autogen} 
     743        execute "ALTER TABLE NAME #{name} TO #{new_name}" 
     744        if pkcol 
     745          change_column_default(new_name,pkcol.name,"UNIQUE") 
     746          begin_db_transaction 
     747          mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}") 
     748          mpk = 0 if mpk.nil? 
     749          execute "SET UNIQUE=#{mpk} FOR #{new_name}" 
     750          commit_db_transaction 
     751        end 
     752      end   
     753 
     754      # Drops a table from the database. 
     755      def drop_table(name) 
     756        execute "DROP TABLE #{name} RESTRICT" 
     757      rescue ActiveRecord::StatementInvalid => e 
     758        a = e.message.scan(/Referenced TABLE - \w* - does not exist/) 
     759        raise e if a.length == 0 
     760      end 
     761 
     762      # Adds a new column to the named table. 
     763      # See TableDefinition#column for details of the options you can use. 
     764      def add_column(table_name, column_name, type, options = {}) 
     765        add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}" 
     766        options[:type] = type 
     767        add_column_options!(add_column_sql, options) 
     768        execute(add_column_sql) 
     769      end 
     770 
     771      def add_column_options!(sql, options) #:nodoc: 
     772        default_value = quote(options[:default], options[:column]) 
     773        if options[:default] 
     774          if options[:type] == :boolean 
     775            default_value = options[:default] == 0 ? quoted_false : quoted_true 
     776          end 
     777        end 
     778        sql << " DEFAULT #{default_value}" unless options[:default].nil? 
     779        sql << " NOT NULL" if options[:null] == false 
     780      end 
     781 
     782      # Removes the column from the table definition. 
     783      # ===== Examples 
     784      #  remove_column(:suppliers, :qualification) 
     785      def remove_column(table_name, column_name) 
     786        execute "ALTER TABLE #{table_name} DROP #{column_name} RESTRICT" 
     787      end 
     788 
     789      def remove_index(table_name, options = {}) #:nodoc: 
     790        if options[:unique] 
     791          execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{quote_column_name(index_name(table_name, options))} RESTRICT" 
     792        else 
     793          execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}" 
     794        end 
     795      end 
     796 
     797      def change_column_default(table_name, column_name, default) #:nodoc: 
     798        execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL" 
     799      end 
     800 
     801      def change_column(table_name, column_name, type, options = {}) #:nodoc: 
     802        change_column_sql = "ALTER COLUMN \"#{table_name}\".\"#{column_name}\" TO #{type_to_sql(type, options[:limit])}" 
     803        execute(change_column_sql) 
     804        change_column_sql = "ALTER TABLE \"#{table_name}\" ALTER COLUMN \"#{column_name}\"" 
     805 
     806        default_value = quote(options[:default], options[:column]) 
     807        if options[:default] 
     808          if type == :boolean 
     809            default_value = options[:default] == 0 ? quoted_false : quoted_true 
     810          end 
     811        end 
     812 
     813        if default_value != "NULL" 
     814          change_column_sql << " SET DEFAULT #{default_value}" 
     815          execute(change_column_sql) 
     816        end 
     817         
     818#         change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}" 
     819#         add_column_options!(change_column_sql, options) 
     820#         execute(change_column_sql) 
     821      end 
     822 
     823      def rename_column(table_name, column_name, new_column_name) #:nodoc: 
     824        execute "ALTER COLUMN NAME \"#{table_name}\".\"#{column_name}\" TO \"#{new_column_name}\"" 
     825      end 
     826             
     827      private 
     828       
     829      # Clean up sql to make it something FrontBase can digest 
     830      def cleanup_fb_sql(sql) #:nodoc: 
     831        cleansql = sql.gsub("!=","<>") # Turn non-standard != into standard <> 
     832        lines = cleansql.split("\n") 
     833        lines = lines.select { |l| l.length > 0} # Strip blank lines 
     834        lines = lines.select { |l| !(l =~ /^--/)} # Strip comments 
     835        cleansql = lines.join("\n") 
     836        cleansql 
     837      end 
     838    end 
     839  end 
     840end 
  • 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.