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

Ticket #4093: patch_against_4280.patch

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

Latest patch as of revision 4280

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

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