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

Ticket #4093: patch_against_4258.patch

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

Latest patch as of revision 4258

  • 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                if ! value.kind_of?(TwelveByteKey) 
     321                  s = value.to_s.unpack("H*")[0] 
     322                  retvalue = "X'#{value.to_s}'" 
     323                else 
     324                  retvalue = "X'#{value.to_s}'" 
     325                end 
     326              when :boolean 
     327                retvalue = quoted_true if value.kind_of?(TrueClass) 
     328                retvalue = quoted_false if value.kind_of?(FalseClass) 
     329              when :binary 
     330                blob_handle = @connection.create_blob(value.to_s) 
     331                retvalue = blob_handle.handle 
     332                puts "SQL -> Insert #{value.to_s.length} byte blob as #{retvalue}" if FB_TRACE 
     333              when :text 
     334                if value.kind_of?(String) 
     335                  clobdata = value.to_s # ' (for ruby-mode) 
     336                else 
     337                  clobdata = value.to_yaml 
     338                end 
     339                clob_handle = @connection.create_clob(clobdata) 
     340                retvalue = clob_handle.handle 
     341                puts "SQL -> Insert #{value.to_s.length} byte clob as #{retvalue}" if FB_TRACE 
     342                 
     343              else 
     344                raise "*** UNKNOWN TYPE: #{column.type.inspect}" 
     345            end # case 
     346          # Since we don't have column type info, make a best guess based 
     347          # on the Ruby class of the value 
     348          else 
     349            case value 
     350              when String 
     351                if column && column.type == :binary 
     352                  s = value.unpack("H*")[0] 
     353                  retvalue = "X'#{s}'" 
     354                elsif column && [:integer, :float].include?(column.type)  
     355                  retvalue = value.to_s 
     356                else 
     357                  retvalue = "'#{quote_string(value)}'" # ' (for ruby-mode) 
     358                end 
     359              when NilClass              then retvalue = "NULL" 
     360              when TrueClass             then retvalue = (column && column.type == :integer ? '1' : quoted_true) 
     361              when FalseClass            then retvalue = (column && column.type == :integer ? '0' : quoted_false) 
     362              when Float, Fixnum, Bignum then retvalue = value.to_s 
     363              when Time, Date, DateTime 
     364                if column 
     365                  case column.type 
     366                    when :date then retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     367                    when :time then retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     368                    when :timestamp then retvalue = "TIMESTAMP '#{value.strftime("%Y-%m-%d %H:%M:%S")}'" 
     369                  else 
     370                    raise NotImplementedError, "Unknown column type!" 
     371                  end # case 
     372                else # Column wasn't passed in, so try to guess the right type 
     373                  if value.kind_of? Date 
     374                    retvalue = "DATE '#{value.strftime("%Y-%m-%d")}'" 
     375                  else 
     376                    if value.hour == 0 && value.min == 0 && value.sec == 0 
     377                      retvalue = "TIME '#{value.strftime("%H:%M:%S")}'" 
     378                    else 
     379                      retvalue = "TIMESTAMP '#{quoted_date(value)}'" 
     380                    end 
     381                  end  
     382                end #if column 
     383              when ActiveRecord::ConnectionAdapters::TwelveByteKey 
     384                s = value.unpack("H*")[0] 
     385                retvalue = "X'#{s}'" 
     386              else "'#{quote_string(value.to_yaml)}'" 
     387            end #case 
     388          end 
     389        else 
     390          retvalue = "NULL" 
     391        end 
     392          
     393      retvalue 
     394      end # def 
     395 
     396      # Quotes a string, escaping any ' (single quote) characters. 
     397      def quote_string(s) 
     398        s.gsub(/'/, "''") # ' (for ruby-mode) 
     399      end 
     400 
     401      def quote_column_name(name) #:nodoc: 
     402        "\"#{name}\"" 
     403      end 
     404 
     405      def quoted_true 
     406        "true" 
     407      end 
     408       
     409      def quoted_false 
     410        "false" 
     411      end 
     412 
     413 
     414      # CONNECTION MANAGEMENT ==================================== 
     415 
     416      def active? 
     417          retvalue = true 
     418          begin 
     419            retvalue = true if @connection.status == 1 
     420          rescue => e 
     421            retvalue = false 
     422          end 
     423          retvalue 
     424      end 
     425 
     426      def reconnect! 
     427        @connection.close rescue nil 
     428      
     429        @connection = FBSQL_Connect.connect(@connection_options[0], 
     430        @connection_options[1], 
     431        @connection_options[2], 
     432        @connection_options[3], 
     433        @connection_options[4], 
     434        @connection_options[5], 
     435        @connection_options[6])         
     436      end 
     437 
     438      # Close this connection 
     439      def disconnect! 
     440        @connection.close rescue nil 
     441        @active = false 
     442      end 
     443 
     444      # DATABASE STATEMENTS ====================================== 
     445 
     446      # Returns an array of record hashes with the column names as keys and 
     447      # column values as values. 
     448      def select_all(sql, name = nil) #:nodoc: 
     449        fbsql = cleanup_fb_sql(sql) 
     450        retValue = [] 
     451        fbresult = execute(sql, name) 
     452         puts "select_all SQL -> #{fbsql}" if FB_TRACE 
     453        columns = fbresult.columns 
     454        fbresult.each do |row| 
     455          puts "SQL <- #{row.inspect}"  if FB_TRACE 
     456          hashed_row = {}     
     457          colnum = 0 
     458          row.each do |col| 
     459            hashed_row[columns[colnum]] = col 
     460            if col.kind_of?(FBSQL_LOB) 
     461              hashed_row[columns[colnum]] = col.read 
     462            end 
     463            colnum += 1 
     464          end 
     465          puts "raw row: #{hashed_row.inspect}" if FB_TRACE 
     466          retValue << hashed_row 
     467        end 
     468        retValue 
     469      end 
     470 
     471      def select_one(sql, name = nil) #:nodoc: 
     472        fbsql = cleanup_fb_sql(sql) 
     473        retValue = [] 
     474        fbresult = execute(fbsql, name) 
     475        puts "SQL -> #{fbsql}"  if FB_TRACE 
     476        columns = fbresult.columns 
     477         
     478        fbresult.each do |row| 
     479           puts "SQL <- #{row.inspect}"  if FB_TRACE 
     480          hashed_row = {}     
     481          colnum = 0 
     482          row.each do |col| 
     483            hashed_row[columns[colnum]] = col 
     484            if col.kind_of?(FBSQL_LOB) 
     485              hashed_row[columns[colnum]] = col.read 
     486            end 
     487            colnum += 1 
     488          end 
     489          retValue << hashed_row 
     490          break 
     491        end 
     492        fbresult.clear 
     493        retValue = retValue.first 
     494      end 
     495 
     496      def query(sql, name = nil) #:nodoc: 
     497        fbsql = cleanup_fb_sql(sql) 
     498        puts "SQL(query) -> #{fbsql}"  if FB_TRACE 
     499        log(fbsql, name) { @connection.query(fbsql) } 
     500      rescue => e 
     501        puts "FB Exception: #{e.inspect}" if FB_TRACE 
     502        raise e 
     503      end 
     504 
     505      def execute(sql, name = nil) #:nodoc: 
     506        fbsql = cleanup_fb_sql(sql) 
     507        puts "SQL(execute) -> #{fbsql}"  if FB_TRACE 
     508        log(fbsql, name) { @connection.query(fbsql) } 
     509      rescue ActiveRecord::StatementInvalid => e 
     510        a = e.message.scan(/Table name - \w* - exists/) 
     511        if a.length == 0 
     512          puts "FB Exception: #{e.inspect}" if FB_TRACE 
     513          raise e 
     514        end 
     515      end 
     516       
     517      # Returns the last auto-generated ID from the affected table. 
     518      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
     519        puts "SQL -> #{sql.inspect}"  if FB_TRACE 
     520        result = execute(sql, name) 
     521        id_value || pk 
     522      end 
     523 
     524      # Executes the update statement and returns the number of rows affected. 
     525      def update(sql, name = nil) #:nodoc: 
     526        puts "SQL -> #{sql.inspect}"  if FB_TRACE 
     527        execute(sql, name).num_rows 
     528      end 
     529 
     530      alias_method :delete, :update #:nodoc: 
     531 
     532      def set_pessimistic_transactions 
     533        if @transaction_mode == :optimistic 
     534          execute "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, LOCKING PESSIMISTIC, READ WRITE" 
     535          @transaction_mode = :pessimistic 
     536        end 
     537      end 
     538 
     539      def set_optimistic_transactions 
     540        if @transaction_mode == :pessimistic 
     541          execute "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, LOCKING OPTIMISTIC" 
     542          @transaction_mode = :optimistic 
     543        end 
     544      end 
     545 
     546      def begin_db_transaction #:nodoc: 
     547        execute "SET COMMIT FALSE" 
     548      rescue 
     549      end 
     550 
     551      def commit_db_transaction #:nodoc: 
     552        execute "COMMIT" 
     553      ensure 
     554        execute "SET COMMIT TRUE" 
     555      end 
     556 
     557      def rollback_db_transaction #:nodoc: 
     558        execute "ROLLBACK" 
     559        ensure 
     560          execute "SET COMMIT TRUE" 
     561      end 
     562 
     563      def add_limit_offset!(sql, options) #:nodoc 
     564        if limit = options[:limit] 
     565          offset = options[:offset] || 0 
     566         
     567# Here is the full syntax FrontBase supports: 
     568# (from gclem@frontbase.com) 
     569#  
     570#       TOP <limit - unsigned integer> 
     571#       TOP ( <offset expr>, <limit expr>) 
     572         
     573          # "TOP 0" is not allowed, so we have 
     574          # to use a cheap trick. 
     575          if limit == 0 
     576            if sql =~ /WHERE/i 
     577              sql.sub!(/WHERE/i, 'WHERE 0 = 1 AND ') 
     578            elsif 
     579              sql =~ /ORDER\s+BY/i 
     580              sql.sub!(/ORDER\s+BY/i, 'WHERE 0 = 1 ORDER BY') 
     581            else 
     582              sql << 'WHERE 0 = 1' 
     583            end 
     584          else 
     585            if offset == 0 
     586              sql.replace sql.gsub("SELECT ","SELECT TOP #{limit} ") 
     587            else 
     588              sql.replace sql.gsub("SELECT ","SELECT TOP(#{offset},#{limit}) ") 
     589          end 
     590        end 
     591           
     592        end 
     593      end 
     594 
     595      def prefetch_primary_key?(table_name = nil) 
     596        true 
     597      end 
     598 
     599      # Returns the next sequence value from a sequence generator. Not generally 
     600      # called directly; used by ActiveRecord to get the next primary key value 
     601      # when inserting a new database record (see #prefetch_primary_key?). 
     602      def next_sequence_value(sequence_name) 
     603        unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") 
     604        # The test cases cannot handle a zero primary key 
     605        if unique == 0 then unique = select_value("SELECT UNIQUE FROM #{sequence_name}","Next Sequence Value") end 
     606        unique  
     607      end 
     608 
     609      def default_sequence_name(table, column) 
     610        table; 
     611      end 
     612 
     613      # Set the sequence to the max value of the table's column. 
     614      def reset_sequence!(table, column, sequence = nil) 
     615        klasses = classes_for_table_name(table) 
     616        klass = klasses.nil? ? nil : klasses[0] 
     617        pk = klass.primary_key if !klass.nil? 
     618        if pk && klass.columns_hash[pk].type == :integer 
     619          execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") 
     620        end 
     621      end 
     622 
     623      def classes_for_table_name(table) 
     624        klasses = [] 
     625        # !!! Hack because subclasses is a protected method... 
     626        subclasses = ActiveRecord::Base.class_eval('@@subclasses') 
     627        subclasses.values.each do |a| 
     628          a.each {|n| klasses << n} 
     629        end 
     630        subset_klasses = klasses.select {|k| k.table_name == table} 
     631      end 
     632       
     633      def reset_pk_sequence!(table, pk = nil, sequence = nil) 
     634        klasses = classes_for_table_name(table) 
     635        klass = klasses.nil? ? nil : klasses[0] 
     636        pk = klass.primary_key if !klass.nil? 
     637        if pk && klass.columns_hash[pk].type == :integer 
     638          mpk = select_value("SELECT MAX(#{pk}) FROM #{table}") 
     639          execute("SET UNIQUE FOR #{klass.table_name}(#{pk})") 
     640        end 
     641         
     642      end 
     643 
     644      # SCHEMA STATEMENTS ======================================== 
     645 
     646      def structure_dump #:nodoc: 
     647        select_all("SHOW TABLES").inject("") do |structure, table| 
     648          structure += select_one("SHOW CREATE TABLE #{table.to_a.first.last}")["Create Table"] + ";\n\n" 
     649        end 
     650      end 
     651 
     652      def recreate_database(name) #:nodoc: 
     653        drop_database(name) 
     654        create_database(name) 
     655      end 
     656 
     657      def create_database(name) #:nodoc: 
     658        execute "CREATE DATABASE #{name}" 
     659      end 
     660       
     661      def drop_database(name) #:nodoc: 
     662        execute "DROP DATABASE #{name}" 
     663      end 
     664 
     665      def current_database 
     666        select_value('SELECT "CATALOG_NAME" FROM INFORMATION_SCHEMA.CATALOGS').downcase 
     667      end 
     668 
     669      def tables(name = nil) #:nodoc: 
     670        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" 
     671         
     672        select_values(sql,nil) 
     673      end 
     674 
     675      def indexes(table_name, name = nil)#:nodoc: 
     676        indexes = [] 
     677        current_index = nil 
     678        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" 
     679 
     680        columns = [] 
     681        query(sql).each do |row| 
     682          index_name   = row[0] 
     683          ord_position = row[1] 
     684          ndx_colcount = row[2] 
     685          index_type   = row[3] 
     686          column_name  = row[4] 
     687           
     688          if index_type == 1 
     689            isUnique = true 
     690          else 
     691            isUnique = false 
     692          end 
     693           
     694          columns << column_name 
     695          if ord_position == ndx_colcount 
     696            indexes << IndexDefinition.new(table_name, index_name, isUnique , columns) 
     697            columns = [] 
     698          end 
     699        end 
     700        indexes 
     701      end 
     702 
     703      def columns(table_name, name = nil)#:nodoc: 
     704        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" 
     705        rawresults = query(sql,name) 
     706        columns = [] 
     707        rawresults.each do |field| 
     708          typestring = field[5] 
     709          typecode = field[6] 
     710          base = field[0] 
     711          name = field[1] 
     712          limit = field[7] 
     713          precision = field[8] 
     714          scale = field[9] 
     715          default = field[4] 
     716          nullable = field[3] 
     717          columns << FrontBaseColumn.new(base, name, typecode, typestring, limit, precision, scale, default, nullable) 
     718         end 
     719        columns 
     720      end 
     721       
     722      def create_table(name, options = {}) 
     723        table_definition = TableDefinition.new(self) 
     724        table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false 
     725 
     726        yield table_definition 
     727 
     728        if options[:force] 
     729          drop_table(name) rescue nil 
     730        end 
     731 
     732        create_sql = "CREATE#{' TEMPORARY' if options[:temporary]} TABLE " 
     733        create_sql << "#{name} (" 
     734        create_sql << table_definition.to_sql 
     735        create_sql << ") #{options[:options]}" 
     736        begin_db_transaction 
     737        execute create_sql 
     738        commit_db_transaction 
     739        rescue ActiveRecord::StatementInvalid => e 
     740          a = e.message.scan(/Table name - \w* - exists/) 
     741          raise e if a.length == 0 
     742      end 
     743       
     744      def rename_table(name, new_name) 
     745        columns = columns(name) 
     746        pkcol = columns.find {|c| c.fb_autogen} 
     747        execute "ALTER TABLE NAME #{name} TO #{new_name}" 
     748        if pkcol 
     749          change_column_default(new_name,pkcol.name,"UNIQUE") 
     750          begin_db_transaction 
     751          mpk = select_value("SELECT MAX(#{pkcol.name}) FROM #{new_name}") 
     752          mpk = 0 if mpk.nil? 
     753          execute "SET UNIQUE=#{mpk} FOR #{new_name}" 
     754          commit_db_transaction 
     755        end 
     756      end   
     757 
     758      # Drops a table from the database. 
     759      def drop_table(name) 
     760        execute "DROP TABLE #{name} RESTRICT" 
     761      rescue ActiveRecord::StatementInvalid => e 
     762        a = e.message.scan(/Referenced TABLE - \w* - does not exist/) 
     763        raise e if a.length == 0 
     764      end 
     765 
     766      # Adds a new column to the named table. 
     767      # See TableDefinition#column for details of the options you can use. 
     768      def add_column(table_name, column_name, type, options = {}) 
     769        add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}" 
     770        options[:type] = type 
     771        add_column_options!(add_column_sql, options) 
     772        execute(add_column_sql) 
     773      end 
     774 
     775      def add_column_options!(sql, options) #:nodoc: 
     776        default_value = quote(options[:default], options[:column]) 
     777        if options[:default] 
     778          if options[:type] == :boolean 
     779            default_value = options[:default] == 0 ? quoted_false : quoted_true 
     780          end 
     781        end 
     782        sql << " DEFAULT #{default_value}" unless options[:default].nil? 
     783        sql << " NOT NULL" if options[:null] == false 
     784      end 
     785 
     786      # Removes the column from the table definition. 
     787      # ===== Examples 
     788      #  remove_column(:suppliers, :qualification) 
     789      def remove_column(table_name, column_name) 
     790        execute "ALTER TABLE #{table_name} DROP #{column_name} RESTRICT" 
     791      end 
     792 
     793      def remove_index(table_name, options = {}) #:nodoc: 
     794        if options[:unique] 
     795          execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{quote_column_name(index_name(table_name, options))} RESTRICT" 
     796        else 
     797          execute "DROP INDEX #{quote_column_name(index_name(table_name, options))}" 
     798        end 
     799      end 
     800 
     801      def change_column_default(table_name, column_name, default) #:nodoc: 
     802        execute "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} SET DEFAULT #{default}" if default != "NULL" 
     803      end 
     804 
     805      def change_column(table_name, column_name, type, options = {}) #:nodoc: 
     806        change_column_sql = "ALTER COLUMN \"#{table_name}\".\"#{column_name}\" TO #{type_to_sql(type, options[:limit])}" 
     807        execute(change_column_sql) 
     808        change_column_sql = "ALTER TABLE \"#{table_name}\" ALTER COLUMN \"#{column_name}\"" 
     809 
     810        default_value = quote(options[:default], options[:column]) 
     811        if options[:default] 
     812          if type == :boolean 
     813            default_value = options[:default] == 0 ? quoted_false : quoted_true 
     814          end 
     815        end 
     816 
     817        if default_value != "NULL" 
     818          change_column_sql << " SET DEFAULT #{default_value}" 
     819          execute(change_column_sql) 
     820        end 
     821         
     822#         change_column_sql = "ALTER TABLE #{table_name} CHANGE #{column_name} #{column_name} #{type_to_sql(type, options[:limit])}" 
     823#         add_column_options!(change_column_sql, options) 
     824#         execute(change_column_sql) 
     825      end 
     826 
     827      def rename_column(table_name, column_name, new_column_name) #:nodoc: 
     828        execute "ALTER COLUMN NAME \"#{table_name}\".\"#{column_name}\" TO \"#{new_column_name}\"" 
     829      end 
     830             
     831      private 
     832       
     833      # Clean up sql to make it something FrontBase can digest 
     834      def cleanup_fb_sql(sql) #:nodoc: 
     835        cleansql = sql.gsub("!=","<>") # Turn non-standard != into standard <> 
     836        lines = cleansql.split("\n") 
     837        lines = lines.select { |l| l.length > 0} # Strip blank lines 
     838        lines = lines.select { |l| !(l =~ /^--/)} # Strip comments 
     839        cleansql = lines.join("\n") 
     840        cleansql 
     841      end 
     842    end 
     843  end 
     844end 
  • 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