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

Ticket #8049: improve_postgresql_adapter.diff

File improve_postgresql_adapter.diff, 58.5 kB (added by roderickvd, 2 years ago)
  • activerecord/test/migration_test.rb

    old new  
    194194      Person.connection.create_table :testings do |t| 
    195195        t.column :foo, :string 
    196196      end 
    197        
    198       con = Person.connection      
     197 
     198      con = Person.connection 
    199199      Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) 
    200200      Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" 
    201201      Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) 
     
    278278 
    279279      # Test for 30 significent digits (beyond the 16 of float), 10 of them 
    280280      # after the decimal place. 
    281       
     281 
    282282      assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth 
    283283 
    284284      assert_equal true, bob.male? 
     
    405405          t.column :url, :string 
    406406        end 
    407407        ActiveRecord::Base.connection.add_index :octopuses, :url 
    408          
     408 
    409409        ActiveRecord::Base.connection.rename_table :octopuses, :octopi 
    410410 
    411411        # Using explicit id in insert for compatibility across all databases 
     
    436436      old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") 
    437437      assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } 
    438438      assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } 
    439       new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns")      
     439      new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") 
    440440      assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } 
    441441      assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } 
    442442      assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } 
    443443    end 
    444      
     444 
    445445    def test_change_column_with_nil_default 
    446446      Person.connection.add_column "people", "contributor", :boolean, :default => true 
    447447      Person.reset_column_information 
    448448      assert Person.new.contributor? 
    449        
     449 
    450450      assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil } 
    451451      Person.reset_column_information 
    452452      assert !Person.new.contributor? 
     
    466466    ensure 
    467467      Person.connection.remove_column("people", "administrator") rescue nil 
    468468    end 
    469      
     469 
    470470    def test_change_column_default 
    471471      Person.connection.change_column_default "people", "first_name", "Tester" 
    472472      Person.reset_column_information 
     
    731731    def test_migrator_with_missing_version_numbers 
    732732      ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 500) 
    733733      assert !Person.column_methods_hash.include?(:middle_name) 
    734        assert_equal 4, ActiveRecord::Migrator.current_version 
    735                          
    736                        ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 2) 
    737                        assert !Reminder.table_exists? 
    738       assert Person.column_methods_hash.include?(:last_name)                    
    739                        assert_equal 2, ActiveRecord::Migrator.current_version 
     734      assert_equal 4, ActiveRecord::Migrator.current_version 
     735 
     736      ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 2) 
     737      assert !Reminder.table_exists? 
     738      assert Person.column_methods_hash.include?(:last_name) 
     739      assert_equal 2, ActiveRecord::Migrator.current_version 
    740740    end 
    741      
     741 
    742742    def test_create_table_with_custom_sequence_name 
    743743      return unless current_adapter? :OracleAdapter 
    744744 
     
    778778 
    779779  end 
    780780end 
    781  
  • activerecord/test/datatype_test_postgresql.rb

    old new  
    11require 'abstract_unit' 
    22 
    3 class PostgresqlDatatype < ActiveRecord::Base 
     3class PostgresqlArray < ActiveRecord::Base 
    44end 
    55 
    6 class PGDataTypeTest < Test::Unit::TestCase 
     6class PostgresqlMoney < ActiveRecord::Base 
     7end 
     8 
     9class PostgresqlNumber < ActiveRecord::Base 
     10end 
     11 
     12class PostgresqlTime < ActiveRecord::Base 
     13end 
     14 
     15class PostgresqlNetworkAddress < ActiveRecord::Base 
     16end 
     17 
     18class PostgresqlBitString < ActiveRecord::Base 
     19end 
     20 
     21class PostgresqlOid < ActiveRecord::Base 
     22end 
     23 
     24class PostgresqlDataTypeTest < Test::Unit::TestCase 
    725  self.use_transactional_fixtures = false 
    826 
    9   TABLE_NAME = 'postgresql_datatypes' 
    10   COLUMNS = [ 
     27  ARRAY_TABLE_NAME = 'postgresql_arrays' 
     28  ARRAY_COLUMNS = [ 
    1129    'id SERIAL PRIMARY KEY', 
    1230    'commission_by_quarter INTEGER[]', 
    1331    'nicknames TEXT[]' 
    1432  ] 
    1533 
     34  MONEY_TABLE_NAME = 'postgresql_moneys' 
     35  MONEY_COLUMNS = [ 
     36    'id SERIAL PRIMARY KEY', 
     37    'wealth MONEY' 
     38  ] 
     39 
     40  NUMBER_TABLE_NAME = 'postgresql_numbers' 
     41  NUMBER_COLUMNS = [ 
     42    'id SERIAL PRIMARY KEY',   
     43    'single REAL', 
     44    'double DOUBLE PRECISION' 
     45  ] 
     46 
     47  TIME_TABLE_NAME = 'postgresql_times' 
     48  TIME_COLUMNS = [ 
     49    'id SERIAL PRIMARY KEY', 
     50    'time_interval INTERVAL' 
     51  ] 
     52 
     53  NETWORK_ADDRESS_TABLE_NAME = 'postgresql_network_addresses' 
     54  NETWORK_ADDRESS_COLUMNS = [ 
     55    'id SERIAL PRIMARY KEY', 
     56    'cidr_address CIDR', 
     57    'inet_address INET', 
     58    'mac_address MACADDR' 
     59  ] 
     60 
     61  BIT_STRING_TABLE_NAME = 'postgresql_bit_strings' 
     62  BIT_STRING_COLUMNS = [ 
     63    'id SERIAL PRIMARY KEY', 
     64    'bit_string BIT(8)', 
     65    'bit_string_varying BIT VARYING(8)' 
     66  ] 
     67 
     68  OID_TABLE_NAME = 'postgresql_oids' 
     69  OID_COLUMNS = [ 
     70    'id SERIAL PRIMARY KEY', 
     71    'obj_id OID' 
     72  ] 
     73 
    1674  def setup 
    1775    @connection = ActiveRecord::Base.connection 
    18     @connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" 
    19     @connection.execute "INSERT INTO #{TABLE_NAME} (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )" 
    20     @first = PostgresqlDatatype.find( 1 ) 
     76 
     77    @connection.execute("CREATE TABLE #{ARRAY_TABLE_NAME} (#{ARRAY_COLUMNS.join(',')})") 
     78    @connection.execute("INSERT INTO #{ARRAY_TABLE_NAME} (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") 
     79    @first_array = PostgresqlArray.find(1) 
     80 
     81    @connection.execute("CREATE TABLE #{MONEY_TABLE_NAME} (#{MONEY_COLUMNS.join(',')})") 
     82    @connection.execute("INSERT INTO #{MONEY_TABLE_NAME} (wealth) VALUES ('$567.89')") 
     83    @connection.execute("INSERT INTO #{MONEY_TABLE_NAME} (wealth) VALUES ('-$567.89')") 
     84    @first_money = PostgresqlMoney.find(1) 
     85    @second_money = PostgresqlMoney.find(2) 
     86 
     87    @connection.execute("CREATE TABLE #{NUMBER_TABLE_NAME} (#{NUMBER_COLUMNS.join(',')})") 
     88    @connection.execute("INSERT INTO #{NUMBER_TABLE_NAME} (single, double) VALUES (123.456, 123456.789)") 
     89    @first_number = PostgresqlNumber.find(1) 
     90 
     91    @connection.execute("CREATE TABLE #{TIME_TABLE_NAME} (#{TIME_COLUMNS.join(',')})") 
     92    @connection.execute("INSERT INTO #{TIME_TABLE_NAME} (time_interval) VALUES ('1 year 2 days ago')") 
     93    @first_time = PostgresqlTime.find(1) 
     94 
     95    @connection.execute("CREATE TABLE #{NETWORK_ADDRESS_TABLE_NAME} (#{NETWORK_ADDRESS_COLUMNS.join(',')})") 
     96    @connection.execute("INSERT INTO #{NETWORK_ADDRESS_TABLE_NAME} (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") 
     97    @first_network_address = PostgresqlNetworkAddress.find(1) 
     98     
     99    @connection.execute("CREATE TABLE #{BIT_STRING_TABLE_NAME} (#{BIT_STRING_COLUMNS.join(',')})") 
     100    @connection.execute("INSERT INTO #{BIT_STRING_TABLE_NAME} (bit_string, bit_string_varying) VALUES (B'00010101', X'15')") 
     101    @first_bit_string = PostgresqlBitString.find(1) 
     102     
     103    @connection.execute("CREATE TABLE #{OID_TABLE_NAME} (#{OID_COLUMNS.join(',')})") 
     104    @connection.execute("INSERT INTO #{OID_TABLE_NAME} (obj_id) VALUES (1234)") 
     105    @first_oid = PostgresqlOid.find(1) 
    21106  end 
    22107 
    23108  def teardown 
    24     @connection.execute "DROP TABLE #{TABLE_NAME}" 
     109    @connection.execute("DROP TABLE #{ARRAY_TABLE_NAME}") 
     110    @connection.execute("DROP TABLE #{MONEY_TABLE_NAME}") 
     111    @connection.execute("DROP TABLE #{NUMBER_TABLE_NAME}") 
     112    @connection.execute("DROP TABLE #{TIME_TABLE_NAME}") 
     113    @connection.execute("DROP TABLE #{NETWORK_ADDRESS_TABLE_NAME}") 
     114    @connection.execute("DROP TABLE #{BIT_STRING_TABLE_NAME}") 
     115    @connection.execute("DROP TABLE #{OID_TABLE_NAME}") 
    25116  end 
    26117 
    27118  def test_data_type_of_array_types 
    28     assert_equal :string, @first.column_for_attribute("commission_by_quarter").type 
    29     assert_equal :string, @first.column_for_attribute("nicknames").type 
     119    assert_equal :string, @first_array.column_for_attribute("commission_by_quarter").type 
     120    assert_equal :string, @first_array.column_for_attribute("nicknames").type 
    30121  end 
    31122 
     123  def test_data_type_of_money_types 
     124    assert_equal :decimal, @first_money.column_for_attribute("wealth").type 
     125  end 
     126 
     127  def test_data_type_of_number_types 
     128    assert_equal :float, @first_number.column_for_attribute("single").type 
     129    assert_equal :float, @first_number.column_for_attribute("double").type 
     130  end 
     131 
     132  def test_data_type_of_time_types 
     133    assert_equal :string, @first_time.column_for_attribute("time_interval").type 
     134  end 
     135 
     136  def test_data_type_of_network_address_types 
     137    assert_equal :string, @first_network_address.column_for_attribute("cidr_address").type 
     138    assert_equal :string, @first_network_address.column_for_attribute("inet_address").type 
     139    assert_equal :string, @first_network_address.column_for_attribute("mac_address").type 
     140  end 
     141 
     142  def test_data_type_of_bit_string_types 
     143    assert_equal :string, @first_bit_string.column_for_attribute("bit_string").type 
     144    assert_equal :string, @first_bit_string.column_for_attribute("bit_string_varying").type 
     145  end 
     146 
     147  def test_data_type_of_oid_types 
     148    assert_equal :integer, @first_oid.column_for_attribute("obj_id").type 
     149  end 
     150 
    32151  def test_array_values 
    33     assert_equal '{35000,21000,18000,17000}', @first.commission_by_quarter 
    34     assert_equal '{foo,bar,baz}', @first.nicknames 
     152    assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter 
     153    assert_equal '{foo,bar,baz}', @first_array.nicknames 
    35154  end 
    36155 
     156  def test_money_values 
     157    assert_equal 567.89, @first_money.wealth 
     158    assert_equal -567.89, @second_money.wealth 
     159  end 
     160 
     161  def test_number_values 
     162    assert_equal 123.456, @first_number.single 
     163    assert_equal 123456.789, @first_number.double 
     164  end 
     165 
     166  def test_time_values 
     167    assert_equal '-1 years -2 days', @first_time.time_interval 
     168  end 
     169 
     170  def test_network_address_values 
     171    assert_equal '192.168.0.0/24', @first_network_address.cidr_address 
     172    assert_equal '172.16.1.254', @first_network_address.inet_address 
     173    assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address 
     174  end 
     175 
     176  def test_bit_string_values 
     177    assert_equal '00010101', @first_bit_string.bit_string 
     178    assert_equal '00010101', @first_bit_string.bit_string_varying 
     179  end 
     180 
     181  def test_oid_values 
     182    assert_equal 1234, @first_oid.obj_id 
     183  end 
     184 
    37185  def test_update_integer_array 
    38186    new_value = '{32800,95000,29350,17000}' 
    39     assert @first.commission_by_quarter = new_value 
    40     assert @first.save 
    41     assert @first.reload 
    42     assert_equal @first.commission_by_quarter, new_value 
     187    assert @first_array.commission_by_quarter = new_value 
     188    assert @first_array.save 
     189    assert @first_array.reload 
     190    assert_equal @first_array.commission_by_quarter, new_value 
    43191  end 
    44192 
    45193  def test_update_text_array 
    46194    new_value = '{robby,robert,rob,robbie}' 
    47     assert @first.nicknames = new_value 
    48     assert @first.save 
    49     assert @first.reload 
    50     assert_equal @first.nicknames, new_value 
     195    assert @first_array.nicknames = new_value 
     196    assert @first_array.save 
     197    assert @first_array.reload 
     198    assert_equal @first_array.nicknames, new_value 
    51199  end 
     200 
     201  def test_update_money 
     202    new_value = 123.45 
     203    assert @first_money.wealth = new_value 
     204    assert @first_money.save 
     205    assert @first_money.reload 
     206    assert_equal @first_money.wealth, new_value 
     207  end 
     208 
     209  def test_update_number 
     210    new_single = 789.012 
     211    new_double = 789012.345 
     212    assert @first_number.single = new_single 
     213    assert @first_number.double = new_double 
     214    assert @first_number.save 
     215    assert @first_number.reload 
     216    assert_equal @first_number.single, new_single 
     217    assert_equal @first_number.double, new_double 
     218  end 
     219 
     220  def test_update_time 
     221    assert @first_time.time_interval = '2 years 3 minutes' 
     222    assert @first_time.save 
     223    assert @first_time.reload 
     224    assert_equal @first_time.time_interval, '2 years 00:03:00' 
     225  end 
     226 
     227  def test_update_network_address 
     228    new_cidr_address = '10.1.2.3/32' 
     229    new_inet_address = '10.0.0.0/8' 
     230    new_mac_address = 'bc:de:f0:12:34:56' 
     231    assert @first_network_address.cidr_address = new_cidr_address 
     232    assert @first_network_address.inet_address = new_inet_address 
     233    assert @first_network_address.mac_address = new_mac_address 
     234    assert @first_network_address.save 
     235    assert @first_network_address.reload 
     236    assert_equal @first_network_address.cidr_address, new_cidr_address 
     237    assert_equal @first_network_address.inet_address, new_inet_address 
     238    assert_equal @first_network_address.mac_address, new_mac_address 
     239  end 
     240 
     241  def test_update_bit_string 
     242    new_bit_string = '11111111' 
     243    new_bit_string_varying = 'FF' 
     244    assert @first_bit_string.bit_string = new_bit_string 
     245    assert @first_bit_string.bit_string_varying = new_bit_string_varying 
     246    assert @first_bit_string.save 
     247    assert @first_bit_string.reload 
     248    assert_equal @first_bit_string.bit_string, new_bit_string 
     249    assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying 
     250  end 
     251 
     252  def test_update_oid 
     253    new_value = 567890 
     254    assert @first_oid.obj_id = new_value 
     255    assert @first_oid.save 
     256    assert @first_oid.reload 
     257    assert_equal @first_oid.obj_id, new_value 
     258  end 
    52259end 
  • activerecord/test/finder_test.rb

    old new  
    232232  end 
    233233 
    234234  def test_bind_enumerable 
     235    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) 
     236 
    235237    assert_equal '1,2,3', bind('?', [1, 2, 3]) 
    236     assert_equal %('a','b','c'), bind('?', %w(a b c)) 
     238    assert_equal quoted_abc, bind('?', %w(a b c)) 
    237239 
    238240    assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) 
    239     assert_equal %('a','b','c'), bind(':a', :a => %w(a b c)) # ' 
     241    assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' 
    240242 
    241243    require 'set' 
    242244    assert_equal '1,2,3', bind('?', Set.new([1, 2, 3])) 
    243     assert_equal %('a','b','c'), bind('?', Set.new(%w(a b c))) 
     245    assert_equal quoted_abc, bind('?', Set.new(%w(a b c))) 
    244246 
    245247    assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3])) 
    246     assert_equal %('a','b','c'), bind(':a', :a => Set.new(%w(a b c))) # ' 
     248    assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # ' 
    247249  end 
    248250 
    249251  def test_bind_empty_enumerable 
     
    254256  end 
    255257 
    256258  def test_bind_string 
    257     assert_equal "''", bind('?', '') 
     259    assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '') 
    258260  end 
    259261 
    260262  def test_bind_record 
     
    267269 
    268270  def test_string_sanitation 
    269271    assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") 
    270     assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") 
     272    assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") 
     273    assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table") 
    271274  end 
    272275 
    273276  def test_count 
  • activerecord/lib/active_record/connection_adapters/abstract/quoting.rb

    old new  
    1111          when String, ActiveSupport::Multibyte::Chars 
    1212            value = value.to_s 
    1313            if column && column.type == :binary && column.class.respond_to?(:string_to_binary) 
    14               "'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode) 
     14              "#{quoted_string_prefix}'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode) 
    1515            elsif column && [:integer, :float].include?(column.type) 
    1616              value = column.type == :integer ? value.to_i : value.to_f 
    1717              value.to_s 
    1818            else 
    19               "'#{quote_string(value)}'" # ' (for ruby-mode) 
     19              "#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode) 
    2020            end 
    2121          when NilClass                 then "NULL" 
    2222          when TrueClass                then (column && column.type == :integer ? '1' : quoted_true) 
     
    2828            if value.acts_like?(:date) || value.acts_like?(:time) 
    2929              "'#{quoted_date(value)}'" 
    3030            else 
    31               "'#{quote_string(value.to_yaml)}'" 
     31              "#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'" 
    3232            end 
    3333        end 
    3434      end 
     
    5656      def quoted_date(value) 
    5757        value.to_s(:db) 
    5858      end 
     59 
     60      def quoted_string_prefix 
     61        '' 
     62      end 
    5963    end 
    6064  end 
    6165end 
  • activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

    old new  
    22 
    33module ActiveRecord 
    44  class Base 
    5     # Establishes a connection to the database that's used by all Active Record objects 
    6     def self.postgresql_connection(config) # :nodoc: 
    7       require_library_or_gem 'postgres' unless self.class.const_defined?(:PGconn) 
     5    # Establishes a connection to the database that's used by all Active Record objects. 
     6    def self.postgresql_connection(config) 
     7      require_library_or_gem('postgres') unless self.class.const_defined?(:PGconn) 
    88 
    99      config = config.symbolize_keys 
    1010      host     = config[:host] 
     
    1212      username = config[:username].to_s 
    1313      password = config[:password].to_s 
    1414 
    15       min_messages = config[:min_messages] 
    16  
    1715      if config.has_key?(:database) 
    1816        database = config[:database] 
    1917      else 
    20         raise ArgumentError, "No database specified. Missing argument: database." 
     18        raise(ArgumentError, 'No database specified. Missing argument: database.') 
    2119      end 
    2220 
    23       pga = ConnectionAdapters::PostgreSQLAdapter.new( 
    24         PGconn.connect(host, port, "", "", database, username, password), logger, config 
    25       ) 
     21      # The postgres drivers don't allow to create an unconnected PGconn object, 
     22      # so just pass a nil connection object for the time being. 
     23      ConnectionAdapters::PostgreSQLAdapter.new(nil, logger, [host, port, nil, nil, database, username, password], config) 
     24    end 
     25  end 
    2626 
    27       PGconn.translate_results = false if PGconn.respond_to? :translate_results= 
     27  module ConnectionAdapters 
     28    # PostgreSQL-specific extensions to column definitions in a table. 
     29    class PostgreSQLColumn < Column #:nodoc: 
     30      # Instantiates a new PostgreSQL column definition in a table. 
     31      def initialize(name, default, sql_type = nil, null = true) 
     32        super(name, self.class.extract_value_from_default(default), sql_type, null) 
     33      end 
    2834 
    29       pga.schema_search_path = config[:schema_search_path] || config[:schema_order] 
     35      private 
     36        # Extracts the scale from PostgreSQL-specific data types. 
     37        def extract_scale(sql_type) 
     38          # Money type has a fixed scale of 2. 
     39          if sql_type =~ /^money/ 
     40            2 
     41          else 
     42            super 
     43          end 
     44        end 
    3045 
    31       pga 
     46        # Extracts the precision from PostgreSQL-specific data types. 
     47        def extract_precision(sql_type) 
     48          # Actual code is defined dynamically in PostgreSQLAdapter.connect 
     49          super 
     50        end 
     51 
     52        # Escapes binary strings for bytea input to the database. 
     53        def self.string_to_binary(value) 
     54          if PGconn.respond_to?(:escape_bytea) 
     55            self.class.module_eval do 
     56              define_method(:string_to_binary) do |value| 
     57                PGconn.escape_bytea(value) if value 
     58              end 
     59            end 
     60          else 
     61            self.class.module_eval do 
     62              define_method(:string_to_binary) do |value| 
     63                if value 
     64                  result = '' 
     65                  value.each_byte { |c| result << sprintf('\\\\%03o', c) } 
     66                  result 
     67                end 
     68              end 
     69            end 
     70          end 
     71          self.class.string_to_binary(value) 
     72        end 
     73 
     74        # Unescapes bytea output from a database to the binary string it represents. 
     75        def self.binary_to_string(value) 
     76          # In each case, check if the value actually is escaped PostgresSQL bytea output 
     77          # or an unescaped Active Record attribute that was just written. 
     78          if PGconn.respond_to?(:unescape_bytea) 
     79            self.class.module_eval do 
     80              define_method(:binary_to_string) do |value| 
     81                if value =~ /\\\\\d{3}/ 
     82                  PGconn.unescape_bytea(value) 
     83                else 
     84                  value 
     85                end 
     86              end 
     87            end 
     88          else 
     89            self.class.module_eval do 
     90              define_method(:binary_to_string) do |value| 
     91                if value =~ /\\\\\d{3}/ 
     92                  result = '' 
     93                  i, max = 0, value.size 
     94                  while i < max 
     95                    char = value[i] 
     96                    if char == ?\\ 
     97                      if value[i+1] == ?\\ 
     98                        char = ?\\ 
     99                        i += 1 
     100                      else 
     101                        char = value[i+1..i+3].oct 
     102                        i += 3 
     103                      end 
     104                    end 
     105                    result << char 
     106                    i += 1 
     107                  end 
     108                  result 
     109                else 
     110                  value 
     111                end 
     112              end 
     113            end 
     114          end 
     115          self.class.binary_to_string(value) 
     116        end 
     117 
     118        # Maps PostgreSQL-specific data types to logical Rails types. 
     119        def simplified_type(field_type) 
     120          case field_type 
     121            # Numeric and monetary types 
     122            when /^(?:real|double precision)$/ 
     123              :float 
     124            # Monetary types 
     125            when /^money$/ 
     126              :decimal 
     127            # Character types 
     128            when /^(?:character varying|bpchar)(?:\(\d+\))?$/ 
     129              :string 
     130            # Binary data types 
     131            when /^bytea$/ 
     132              :binary 
     133            # Date/time types 
     134            when /^timestamp with(?:out)? time zone$/ 
     135              :datetime 
     136            when /^interval$/ 
     137              :string 
     138            # Geometric types 
     139            when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ 
     140              :string 
     141            # Network address types 
     142            when /^(?:cidr|inet|macaddr)$/ 
     143              :string 
     144            # Bit strings 
     145            when /^bit(?: varying)?(?:\(\d+\))?$/ 
     146              :string 
     147            # XML type 
     148            when /^xml$/ 
     149              :string 
     150            # Arrays 
     151            when /^\D+\[\]$/ 
     152              :string               
     153            # Object identifier types 
     154            when /^oid$/ 
     155              :integer 
     156            # Pass through all types that are not specific to PostgreSQL. 
     157            else 
     158              super 
     159          end 
     160        end 
     161 
     162        # Extracts the value from a PostgreSQL column default definition. 
     163        def self.extract_value_from_default(default) 
     164          case default 
     165            # Numeric types 
     166            when /^-?\d+(\.\d*)?$/ 
     167              default 
     168            # Character types 
     169            when /^'(.*)'::(?:character varying|bpchar|text)$/ 
     170              $1 
     171            # Binary data types 
     172            when /^'(.*)'::bytea$/ 
     173              $1 
     174            # Date/time types 
     175            when /^'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)$/ 
     176              $1 
     177            when /^'(.*)'::interval$/ 
     178              $1 
     179            # Boolean type 
     180            when /^true$/ 
     181              true 
     182            when /^false$/ 
     183              false 
     184            # Geometric types 
     185            when /^'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)$/ 
     186              $1 
     187            # Network address types 
     188            when /^'(.*)'::(?:cidr|inet|macaddr)$/ 
     189              $1 
     190            # Bit string types 
     191            when /^B'(.*)'::"?bit(?: varying)?"?$/ 
     192              $1 
     193            # XML type 
     194            when /^'(.*)'::xml$/ 
     195              $1 
     196            # Arrays 
     197            when /^'(.*)'::"?\D+"?\[\]$/ 
     198              $1 
     199            # Object identifier types 
     200            when /^-?\d+$/ 
     201              $1 
     202            else 
     203              # Anything else is blank, some user type, or some function 
     204              # and we can't know the value of that, so return nil.             
     205              nil 
     206          end 
     207        end 
    32208    end 
    33   end 
    34209 
    35   module ConnectionAdapters 
    36     # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base 
    37     # (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers. 
     210    # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure 
     211    # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers. 
    38212    # 
    39213    # Options: 
    40214    # 
     
    48222    # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection. 
    49223    # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. 
    50224    class PostgreSQLAdapter < AbstractAdapter 
     225      # Returns 'PostgreSQL' as adapter name for identification purposes. 
    51226      def adapter_name 
    52227        'PostgreSQL' 
    53228      end 
    54229 
    55       def initialize(connection, logger, config = {}) 
     230      # Initializes and connects a PostgreSQL adapter. 
     231      def initialize(connection, logger, connection_parameters, config) 
    56232        super(connection, logger) 
    57         @config = config 
     233        @connection_parameters, @config = connection_parameters, config 
    58234 
    59         # Ignore async_exec and async_query with the postgres-pr client lib. 
    60         @async = config[:allow_concurrency] && @connection.respond_to?(:async_exec) 
    61  
    62         configure_connection 
     235        connect 
    63236      end 
    64237 
    65       # Is this connection alive and ready for queries? 
     238      # Returns if this connection is alive and ready for queries. 
    66239      def active? 
    67240        if @connection.respond_to?(:status) 
    68241          @connection.status == PGconn::CONNECTION_OK 
    69242        else 
    70           @connection.query 'SELECT 1' 
     243          # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query 
     244          @connection.query('SELECT 1') 
    71245          true 
    72246        end 
    73       # postgres-pr raises a NoMethodError when querying if no conn is available 
     247      # postgres-pr raises a NoMethodError when querying if no connection is available. 
    74248      rescue PGError, NoMethodError 
    75249        false 
    76250      end 
    77251 
    78       # Close then reopen the connection. 
     252      # Closes and then reopens the connection. 
    79253      def reconnect! 
    80         # TODO: postgres-pr doesn't have PGconn#reset. 
    81254        if @connection.respond_to?(:reset) 
    82255          @connection.reset 
    83256          configure_connection 
     257        else 
     258          disconnect! 
     259          connect 
    84260        end 
    85261      end 
    86262 
     263      # Closes the connection. 
    87264      def disconnect! 
    88         # Both postgres and postgres-pr respond to :close 
    89265        @connection.close rescue nil 
    90266      end 
    91267 
    92       def native_database_types 
     268      def native_database_types #:nodoc: 
    93269        { 
    94           :primary_key => "serial primary key"
    95           :string      => { :name => "character varying", :limit => 255 }, 
    96           :text        => { :name => "text" }, 
    97           :integer     => { :name => "integer" }, 
    98           :float       => { :name => "float" }, 
    99           :decimal     => { :name => "decimal" }, 
    100           :datetime    => { :name => "timestamp" }, 
    101           :timestamp   => { :name => "timestamp" }, 
    102           :time        => { :name => "time" }, 
    103           :date        => { :name => "date" }, 
    104           :binary      => { :name => "bytea" }, 
    105           :boolean     => { :name => "boolean"
     270          :primary_key => 'serial PRIMARY KEY'
     271          :string      => { :name => 'character varying', :limit => 255 }, 
     272          :text        => { :name => 'text' }, 
     273          :integer     => { :name => 'integer' }, 
     274          :float       => { :name => 'float' }, 
     275          :decimal     => { :name => 'decimal' }, 
     276          :datetime    => { :name => 'timestamp' }, 
     277          :timestamp   => { :name => 'timestamp' }, 
     278          :time        => { :name => 'time' }, 
     279          :date        => { :name => 'date' }, 
     280          :binary      => { :name => 'bytea' }, 
     281          :boolean     => { :name => 'boolean'
    106282        } 
    107283      end 
    108284 
     285      # Returns true to indicate that PostgreSQL supports migrations. 
    109286      def supports_migrations? 
    110287        true 
    111288      end 
    112289 
     290      # Returns the configured supported identifier length supported by PostgreSQL, 
     291      # or report the default of 63 on PostgreSQL 7. 
    113292      def table_alias_length 
    114         63 
     293        @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63) 
    115294      end 
    116295 
    117296      # QUOTING ================================================== 
    118297 
    119       def quote(value, column = nil) 
     298      # Quotes PostgreSQL-specific data types for SQL input. 
     299      def quote(value, column = nil) #:nodoc: 
    120300        if value.kind_of?(String) && column && column.type == :binary 
    121           "'#{escape_bytea(value)}'" 
     301          "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'" 
     302        elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ 
     303          "xml '#{quote_string(value)}'" 
     304        elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ 
     305          # Not truly string input, so doesn't require (or allow) escape string syntax. 
     306          "'#{value.to_s}'" 
     307        elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/ 
     308          case value 
     309            when /^[01]*$/ 
     310              "B'#{value}'" # Bit-string notation 
     311            when /^[0-9A-F]*$/i 
     312              "X'#{value}'" # Hexadecimal notation 
     313          end 
    122314        else 
    123315          super 
    124316        end 
    125317      end 
    126318 
    127       def quote_column_name(name) 
     319      # Quotes strings for use in SQL input in the postgres driver for better performance. 
     320      def quote_string(s) #:nodoc: 
     321        if PGconn.respond_to?(:escape) 
     322          self.class.instance_eval do 
     323            define_method(:quote_string) do |s| 
     324              PGconn.escape(s) 
     325            end 
     326          end 
     327        else 
     328          # There are some incorrectly compiled postgres drivers out there 
     329          # that don't define PGconn.escape. 
     330          self.class.instance_eval do 
     331            undef_method(:quote_string) 
     332          end 
     333        end 
     334        quote_string(s) 
     335      end 
     336 
     337      # Quotes column names for use in SQL queries. 
     338      def quote_column_name(name) #:nodoc: 
    128339        %("#{name}") 
    129340      end 
    130341 
    131       # Include microseconds if the value is a Time responding to usec. 
    132       def quoted_date(value) 
     342      # Quote date/time values for use in SQL input. Includes microseconds 
     343      # if the value is a Time responding to usec. 
     344      def quoted_date(value) #:nodoc: 
    133345        if value.acts_like?(:time) && value.respond_to?(:usec) 
    134346          "#{super}.#{sprintf("%06d", value.usec)}" 
    135347        else 
     
    137349        end 
    138350      end 
    139351 
    140  
    141352      # DATABASE STATEMENTS ====================================== 
    142353 
    143       def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
     354      # Executes an INSERT query and returns the new record's ID 
     355      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 
    144356        execute(sql, name) 
    145         table = sql.split(" ", 4)[2] 
     357        table = sql.split(' ', 4)[2] 
    146358        id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) 
    147359      end 
    148360 
     361      # Queries the database and returns the results in an Array or nil otherwise. 
    149362      def query(sql, name = nil) #:nodoc: 
    150363        log(sql, name) do 
    151364          if @async 
     
    156369        end 
    157370      end 
    158371 
    159       def execute(sql, name = nil) #:nodoc: 
     372      # Executes a SQL statement, returning a PGresult object on success 
     373      # or raising a PGError exception otherwise. 
     374      def execute(sql, name = nil) 
    160375        log(sql, name) do 
    161376          if @async 
    162377            @connection.async_exec(sql) 
     
    166381        end 
    167382      end 
    168383 
    169       def update(sql, name = nil) #:nodoc: 
     384      # Executes an UPDATE query and returns the number of affected tuples. 
     385      def update(sql, name = nil) 
    170386        execute(sql, name).cmdtuples 
    171387      end 
    172388 
    173       def begin_db_transaction #:nodoc: 
    174         execute "BEGIN" 
     389      # Begins a transaction. 
     390      def begin_db_transaction 
     391        execute('BEGIN') 
    175392      end 
    176393 
    177       def commit_db_transaction #:nodoc: 
    178         execute "COMMIT" 
     394      # Commits a transaction. 
     395      def commit_db_transaction 
     396        execute('COMMIT') 
    179397      end 
    180398 
    181       def rollback_db_transaction #:nodoc: 
    182         execute "ROLLBACK" 
     399      # Aborts a transaction. 
     400      def rollback_db_transaction 
     401        execute('ROLLBACK') 
    183402      end 
    184403 
    185404      # SCHEMA STATEMENTS ======================================== 
    186405 
    187       # Return the list of all tables in the schema search path
    188       def tables(name = nil) #:nodoc: 
     406      # Returns the list of all tables in the schema search path or a specified schema
     407      def tables(name = nil) 
    189408        schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') 
    190409        query(<<-SQL, name).map { |row| row[0] } 
    191410          SELECT tablename 
     
    194413        SQL 
    195414      end 
    196415 
    197       def indexes(table_name, name = nil) #:nodoc: 
     416      # Returns the list of all indexes for a table. 
     417      def indexes(table_name, name = nil) 
    198418        result = query(<<-SQL, name) 
    199419          SELECT i.relname, d.indisunique, a.attname 
    200420            FROM pg_class t, pg_class i, pg_index d, pg_attribute a 
     
    217437 
    218438        result.each do |row| 
    219439          if current_index != row[0] 
    220             indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", []) 
     440            indexes << IndexDefinition.new(table_name, row[0], row[1] == 't', []) 
    221441            current_index = row[0] 
    222442          end 
    223443 
     
    227447        indexes 
    228448      end 
    229449 
    230       def columns(table_name, name = nil) #:nodoc: 
    231         column_definitions(table_name).collect do |name, type, default, notnull, typmod| 
    232           # typmod now unused as limit, precision, scale all handled by superclass 
    233           Column.new(name, default_value(default), translate_field_type(type), notnull == "f") 
     450      # Returns the list of all column definitions for a table. 
     451      def columns(table_name, name = nil) 
     452        # Limit, precision, and scale are all handled by superclass. 
     453        column_definitions(table_name).collect do |name, type, default, notnull| 
     454          PostgreSQLColumn.new(name, default, type, notnull == 'f') 
    234455        end 
    235456      end 
    236457 
    237       # Set the schema search path to a string of comma-separated schema names. 
    238       # Names beginning with $ are quoted (e.g. $user => '$user') 
    239       # See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html 
    240       def schema_search_path=(schema_csv) #:nodoc: 
     458      # Sets the schema search path to a string of comma-separated schema names. 
     459      # Names beginning with $ have to be quoted (e.g. $user => '$user'). 
     460      # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html 
     461      # 
     462      # This should be not be called manually but set in database.yml. 
     463      def schema_search_path=(schema_csv) 
    241464        if schema_csv 
    242           execute "SET search_path TO #{schema_csv}" 
    243           @schema_search_path = nil 
     465          execute("SET search_path TO #{schema_csv}") 
     466          @schema_search_path = schema_csv 
    244467        end 
    245468      end 
    246469 
    247       def schema_search_path #:nodoc: 
     470      # Returns the active schema search path. 
     471      def schema_search_path 
    248472        @schema_search_path ||= query('SHOW search_path')[0][0] 
    249473      end 
    250474 
    251       def default_sequence_name(table_name, pk = nil) 
     475      # Returns the sequence name for a table's primary key or some other specified key. 
     476      def default_sequence_name(table_name, pk = nil) #:nodoc: 
    252477        default_pk, default_seq = pk_and_sequence_for(table_name) 
    253478        default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq" 
    254479      end 
    255480 
    256       # Resets sequence to the max value of the table's pk if present
    257       def reset_pk_sequence!(table, pk = nil, sequence = nil) 
    258         unless pk and sequence 
     481      # Resets the sequence of a table's primary key to the maximum value
     482      def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: 
     483        unless pk && sequence 
    259484          default_pk, default_sequence = pk_and_sequence_for(table) 
    260485          pk ||= default_pk 
    261486          sequence ||= default_sequence 
    262487        end 
    263488        if pk 
    264489          if sequence 
    265             select_value <<-end_sql, 'Reset sequence' 
     490            select_value(<<-end_sql, 'Reset sequence') 
    266491              SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false) 
    267492            end_sql 
    268493          else 
    269             @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger 
     494            @logger.warn("#{table} has primary key #{pk} with no default sequence") if @logger 
    270495          end 
    271496        end 
    272497      end 
    273498