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

Ticket #8049: functionally_improve_postgresql_adapter.diff

File functionally_improve_postgresql_adapter.diff, 49.8 kB (added by roderickvd, 1 year ago)

Functional changes to the PostgreSQL adapter

  • 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 = [ 
    11     'id SERIAL PRIMARY KEY', 
    12     'commission_by_quarter INTEGER[]', 
    13     'nicknames TEXT[]' 
    14   ] 
    15  
    1627  def setup 
    1728    @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 ) 
    21   end 
    2229 
    23   def teardown 
    24     @connection.execute "DROP TABLE #{TABLE_NAME}" 
     30    @connection.execute("INSERT INTO postgresql_arrays (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") 
     31    @first_array = PostgresqlArray.find(1) 
     32 
     33    @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('$567.89')") 
     34    @connection.execute("INSERT INTO postgresql_moneys (wealth) VALUES ('-$567.89')") 
     35    @first_money = PostgresqlMoney.find(1) 
     36    @second_money = PostgresqlMoney.find(2) 
     37 
     38    @connection.execute("INSERT INTO postgresql_numbers (single, double) VALUES (123.456, 123456.789)") 
     39    @first_number = PostgresqlNumber.find(1) 
     40 
     41    @connection.execute("INSERT INTO postgresql_times (time_interval) VALUES ('1 year 2 days ago')") 
     42    @first_time = PostgresqlTime.find(1) 
     43 
     44    @connection.execute("INSERT INTO postgresql_network_address (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") 
     45    @first_network_address = PostgresqlNetworkAddress.find(1) 
     46     
     47    @connection.execute("INSERT INTO postgresql_bit_strings (bit_string, bit_string_varying) VALUES (B'00010101', X'15')") 
     48    @first_bit_string = PostgresqlBitString.find(1) 
     49     
     50    @connection.execute("INSERT INTO postgresql_oids (obj_id) VALUES (1234)") 
     51    @first_oid = PostgresqlOid.find(1) 
    2552  end 
    2653 
    2754  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 
     55    assert_equal :string, @first_array.column_for_attribute(:commission_by_quarter).type 
     56    assert_equal :string, @first_array.column_for_attribute(:nicknames).type 
    3057  end 
    3158 
     59  def test_data_type_of_money_types 
     60    assert_equal :decimal, @first_money.column_for_attribute(:wealth).type 
     61  end 
     62 
     63  def test_data_type_of_number_types 
     64    assert_equal :float, @first_number.column_for_attribute(:single).type 
     65    assert_equal :float, @first_number.column_for_attribute(:double).type 
     66  end 
     67 
     68  def test_data_type_of_time_types 
     69    assert_equal :string, @first_time.column_for_attribute(:time_interval).type 
     70  end 
     71 
     72  def test_data_type_of_network_address_types 
     73    assert_equal :string, @first_network_address.column_for_attribute(:cidr_address).type 
     74    assert_equal :string, @first_network_address.column_for_attribute(:inet_address).type 
     75    assert_equal :string, @first_network_address.column_for_attribute(:mac_address).type 
     76  end 
     77 
     78  def test_data_type_of_bit_string_types 
     79    assert_equal :string, @first_bit_string.column_for_attribute(:bit_string).type 
     80    assert_equal :string, @first_bit_string.column_for_attribute(:bit_string_varying).type 
     81  end 
     82 
     83  def test_data_type_of_oid_types 
     84    assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type 
     85  end 
     86 
    3287  def test_array_values 
    33     assert_equal '{35000,21000,18000,17000}', @first.commission_by_quarter 
    34     assert_equal '{foo,bar,baz}', @first.nicknames 
     88   assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter 
     89   assert_equal '{foo,bar,baz}', @first_array.nicknames 
    3590  end 
    3691 
     92  def test_money_values 
     93    assert_equal 567.89, @first_money.wealth 
     94    assert_equal -567.89, @second_money.wealth 
     95  end 
     96 
     97  def test_number_values 
     98    assert_equal 123.456, @first_number.single 
     99    assert_equal 123456.789, @first_number.double 
     100  end 
     101 
     102  def test_time_values 
     103    assert_equal '-1 years -2 days', @first_time.time_interval 
     104  end 
     105 
     106  def test_network_address_values 
     107    assert_equal '192.168.0.0/24', @first_network_address.cidr_address 
     108    assert_equal '172.16.1.254', @first_network_address.inet_address 
     109    assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address 
     110  end 
     111 
     112  def test_bit_string_values 
     113    assert_equal '00010101', @first_bit_string.bit_string 
     114    assert_equal '00010101', @first_bit_string.bit_string_varying 
     115  end 
     116 
     117  def test_oid_values 
     118    assert_equal 1234, @first_oid.obj_id 
     119  end 
     120 
    37121  def test_update_integer_array 
    38122    new_value = '{32800,95000,29350,17000}' 
    39123    assert @first.commission_by_quarter = new_value 
    40124    assert @first.save 
    41125    assert @first.reload 
    42126    assert_equal @first.commission_by_quarter, new_value 
     127    assert @first_array.commission_by_quarter = new_value 
     128    assert @first_array.save 
     129    assert @first_array.reload 
     130    assert_equal @first_array.commission_by_quarter, new_value 
    43131  end 
    44132 
    45133  def test_update_text_array 
     
    48136    assert @first.save 
    49137    assert @first.reload 
    50138    assert_equal @first.nicknames, new_value 
     139    assert @first_array.nicknames = new_value 
     140    assert @first_array.save 
     141    assert @first_array.reload 
     142    assert_equal @first_array.nicknames, new_value 
    51143  end 
     144 
     145  def test_update_money 
     146    new_value = 123.45 
     147    assert @first_money.wealth = new_value 
     148    assert @first_money.save 
     149    assert @first_money.reload 
     150    assert_equal @first_money.wealth, new_value 
     151  end 
     152 
     153  def test_update_number 
     154    new_single = 789.012 
     155    new_double = 789012.345 
     156    assert @first_number.single = new_single 
     157    assert @first_number.double = new_double 
     158    assert @first_number.save 
     159    assert @first_number.reload 
     160    assert_equal @first_number.single, new_single 
     161    assert_equal @first_number.double, new_double 
     162  end 
     163 
     164  def test_update_time 
     165    assert @first_time.time_interval = '2 years 3 minutes' 
     166    assert @first_time.save 
     167    assert @first_time.reload 
     168    assert_equal @first_time.time_interval, '2 years 00:03:00' 
     169  end 
     170 
     171  def test_update_network_address 
     172    new_cidr_address = '10.1.2.3/32' 
     173    new_inet_address = '10.0.0.0/8' 
     174    new_mac_address = 'bc:de:f0:12:34:56' 
     175    assert @first_network_address.cidr_address = new_cidr_address 
     176    assert @first_network_address.inet_address = new_inet_address 
     177    assert @first_network_address.mac_address = new_mac_address 
     178    assert @first_network_address.save 
     179    assert @first_network_address.reload 
     180    assert_equal @first_network_address.cidr_address, new_cidr_address 
     181    assert_equal @first_network_address.inet_address, new_inet_address 
     182    assert_equal @first_network_address.mac_address, new_mac_address 
     183  end 
     184 
     185  def test_update_bit_string 
     186    new_bit_string = '11111111' 
     187    new_bit_string_varying = 'FF' 
     188    assert @first_bit_string.bit_string = new_bit_string 
     189    assert @first_bit_string.bit_string_varying = new_bit_string_varying 
     190    assert @first_bit_string.save 
     191    assert @first_bit_string.reload 
     192    assert_equal @first_bit_string.bit_string, new_bit_string 
     193    assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying 
     194  end 
     195 
     196  def test_update_oid 
     197    new_value = 567890 
     198    assert @first_oid.obj_id = new_value 
     199    assert @first_oid.save 
     200    assert @first_oid.reload 
     201    assert_equal @first_oid.obj_id, new_value 
     202  end 
    52203end 
  • activerecord/test/finder_test.rb

    old new  
    267267  end 
    268268 
    269269  def test_bind_enumerable 
     270    quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) 
     271 
    270272    assert_equal '1,2,3', bind('?', [1, 2, 3]) 
    271     assert_equal %('a','b','c'), bind('?', %w(a b c)) 
     273    assert_equal quoted_abc, bind('?', %w(a b c)) 
    272274 
    273275    assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) 
    274     assert_equal %('a','b','c'), bind(':a', :a => %w(a b c)) # ' 
     276    assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' 
    275277 
    276278    require 'set' 
    277279    assert_equal '1,2,3', bind('?', Set.new([1, 2, 3])) 
    278     assert_equal %('a','b','c'), bind('?', Set.new(%w(a b c))) 
     280    assert_equal quoted_abc, bind('?', Set.new(%w(a b c))) 
    279281 
    280282    assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3])) 
    281     assert_equal %('a','b','c'), bind(':a', :a => Set.new(%w(a b c))) # ' 
     283    assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # ' 
    282284  end 
    283285 
    284286  def test_bind_empty_enumerable 
     
    289291  end 
    290292 
    291293  def test_bind_string 
    292     assert_equal "''", bind('?', '') 
     294    assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '') 
    293295  end 
    294296 
    295297  def test_bind_record 
     
    301303  end 
    302304 
    303305  def test_string_sanitation 
    304     assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") 
    305     assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") 
     306    assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") 
     307    assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table") 
    306308  end 
    307309 
    308310  def test_count 
  • activerecord/test/fixtures/db_definitions/postgresql.sql

    old new  
    245245 "monkeyID" INTEGER PRIMARY KEY, 
    246246 "fleaCount" INTEGER 
    247247); 
     248 
     249CREATE TABLE postgresql_arrays ( 
     250  id SERIAL PRIMARY KEY, 
     251  commission_by_quarter INTEGER[], 
     252  nicknames TEXT[] 
     253); 
     254 
     255CREATE TABLE postgresql_moneys ( 
     256  id SERIAL PRIMARY KEY, 
     257  wealth MONEY 
     258); 
     259 
     260CREATE TABLE postgresql_numbers ( 
     261  id SERIAL PRIMARY KEY, 
     262  single REAL, 
     263  double DOUBLE PRECISION 
     264); 
     265 
     266CREATE TABLE postgresql_times ( 
     267  SERIAL PRIMARY KEY, 
     268  time_interval INTERVAL 
     269); 
     270 
     271CREATE TABLE postgresql_network_addresses ( 
     272  id SERIAL PRIMARY KEY, 
     273  cidr_address CIDR, 
     274  inet_address INET, 
     275  mac_address MACADDR 
     276); 
     277 
     278CREATE TABLE postgresql_bit_strings ( 
     279  id SERIAL PRIMARY KEY, 
     280  bit_string BIT(8), 
     281  bit_string_varying BIT VARYING(8) 
     282); 
     283 
     284CREATE TABLE postgresql_oids ( 
     285  id SERIAL PRIMARY KEY, 
     286  obj_id OID 
     287); 
  • activerecord/test/fixtures/db_definitions/postgresql.drop.sql

    old new  
    3535DROP TABLE numeric_data; 
    3636DROP TABLE column_data; 
    3737DROP TABLE mixed_case_monkeys; 
     38DROP TABLE postgresql_arrays; 
     39DROP TABLE postgresql_moneys; 
     40DROP TABLE postgresql_numbers; 
     41DROP TABLE postgresql_times; 
     42DROP TABLE postgresql_network_addresses; 
     43DROP TABLE postgresql_bit_strings; 
     44DROP TABLE postgresql_oids; 
  • 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  
    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 
    2018        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          sql_type =~ /^money/ ? 2 : super 
     40        end 
    3041 
    31       pga 
     42        # Extracts the precision from PostgreSQL-specific data types. 
     43        def extract_precision(sql_type) 
     44          # Actual code is defined dynamically in PostgreSQLAdapter.connect 
     45          # depending on the server specifics 
     46          super 
     47        end 
     48   
     49        # Escapes binary strings for bytea input to the database. 
     50        def self.string_to_binary(value) 
     51          if PGconn.respond_to?(:escape_bytea) 
     52            self.class.module_eval do 
     53              define_method(:string_to_binary) do |value| 
     54                PGconn.escape_bytea(value) if value 
     55              end 
     56            end 
     57          else 
     58            self.class.module_eval do 
     59              define_method(:string_to_binary) do |value| 
     60                if value 
     61                  result = '' 
     62                  value.each_byte { |c| result << sprintf('\\\\%03o', c) } 
     63                  result 
     64                end 
     65              end 
     66            end 
     67          end 
     68          self.class.string_to_binary(value) 
     69        end 
     70   
     71        # Unescapes bytea output from a database to the binary string it represents. 
     72        def self.binary_to_string(value) 
     73          # In each case, check if the value actually is escaped PostgresSQL bytea output 
     74          # or an unescaped Active Record attribute that was just written. 
     75          if PGconn.respond_to?(:unescape_bytea) 
     76            self.class.module_eval do 
     77              define_method(:binary_to_string) do |value| 
     78                if value =~ /\\\\\d{3}/ 
     79                  PGconn.unescape_bytea(value) 
     80                else 
     81                  value 
     82                end 
     83              end 
     84            end 
     85          else 
     86            self.class.module_eval do 
     87              define_method(:binary_to_string) do |value| 
     88                if value =~ /\\\\\d{3}/ 
     89                  result = '' 
     90                  i, max = 0, value.size 
     91                  while i < max 
     92                    char = value[i] 
     93                    if char == ?\\ 
     94                      if value[i+1] == ?\\ 
     95                        char = ?\\ 
     96                        i += 1 
     97                      else 
     98                        char = value[i+1..i+3].oct 
     99                        i += 3 
     100                      end 
     101                    end 
     102                    result << char 
     103                    i += 1 
     104                  end 
     105                  result 
     106                else 
     107                  value 
     108                end 
     109              end 
     110            end 
     111          end 
     112          self.class.binary_to_string(value) 
     113        end   
     114   
     115        # Maps PostgreSQL-specific data types to logical Rails types. 
     116        def simplified_type(field_type) 
     117          case field_type 
     118            # Numeric and monetary types 
     119            when /^(?:real|double precision)$/ 
     120              :float 
     121            # Monetary types 
     122            when /^money$/ 
     123              :decimal 
     124            # Character types 
     125            when /^(?:character varying|bpchar)(?:\(\d+\))?$/ 
     126              :string 
     127            # Binary data types 
     128            when /^bytea$/ 
     129              :binary 
     130            # Date/time types 
     131            when /^timestamp with(?:out)? time zone$/ 
     132              :datetime 
     133            when /^interval$/ 
     134              :string 
     135            # Geometric types 
     136            when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ 
     137              :string 
     138            # Network address types 
     139            when /^(?:cidr|inet|macaddr)$/ 
     140              :string 
     141            # Bit strings 
     142            when /^bit(?: varying)?(?:\(\d+\))?$/ 
     143              :string 
     144            # XML type 
     145            when /^xml$/ 
     146              :string 
     147            # Arrays 
     148            when /^\D+\[\]$/ 
     149              :string               
     150            # Object identifier types 
     151            when /^oid$/ 
     152              :integer 
     153            # Pass through all types that are not specific to PostgreSQL. 
     154            else 
     155              super 
     156          end 
     157        end 
     158   
     159        # Extracts the value from a PostgreSQL column default definition. 
     160        def self.extract_value_from_default(default) 
     161          case default 
     162            # Numeric types 
     163            when /^-?\d+(\.\d*)?$/ 
     164              default 
     165            # Character types 
     166            when /^'(.*)'::(?:character varying|bpchar|text)$/ 
     167              $1 
     168            # Binary data types 
     169            when /^'(.*)'::bytea$/ 
     170              $1 
     171            # Date/time types 
     172            when /^'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)$/ 
     173              $1 
     174            when /^'(.*)'::interval$/ 
     175              $1 
     176            # Boolean type 
     177            when /^true$/ 
     178              true 
     179            when /^false$/ 
     180              false 
     181            # Geometric types 
     182            when /^'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)$/ 
     183              $1 
     184            # Network address types 
     185            when /^'(.*)'::(?:cidr|inet|macaddr)$/ 
     186              $1 
     187            # Bit string types 
     188            when /^B'(.*)'::"?bit(?: varying)?"?$/ 
     189              $1 
     190            # XML type 
     191            when /^'(.*)'::xml$/ 
     192              $1 
     193            # Arrays 
     194            when /^'(.*)'::"?\D+"?\[\]$/ 
     195              $1 
     196            # Object identifier types 
     197            when /^-?\d+$/ 
     198              $1 
     199            else 
     200              # Anything else is blank, some user type, or some function 
     201              # and we can't know the value of that, so return nil.             
     202              nil 
     203          end 
     204        end 
    32205    end 
    33206  end 
    34207 
    35208  module ConnectionAdapters 
    36     # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-bas
    37     # (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers. 
     209    # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pur
     210    # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers. 
    38211    # 
    39212    # Options: 
    40213    # 
     
    48221    # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection. 
    49222    # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. 
    50223    class PostgreSQLAdapter < AbstractAdapter 
     224      # Returns 'PostgreSQL' as adapter name for identification purposes. 
    51225      def adapter_name 
    52226        'PostgreSQL' 
    53227      end 
    54228 
    55       def initialize(connection, logger, config = {}) 
     229      # Initializes and connects a PostgreSQL adapter. 
     230      def initialize(connection, logger, connection_parameters, config) 
    56231        super(connection, logger) 
    57         @config = config 
     232        @connection_parameters, @config = connection_parameters, config 
    58233 
    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 
     234        connect 
    63235      end 
    64236 
    65237      # Is this connection alive and ready for queries? 
     
    67239        if @connection.respond_to?(:status) 
    68240          @connection.status == PGconn::CONNECTION_OK 
    69241        else 
     242          # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query 
    70243          @connection.query 'SELECT 1' 
    71244          true 
    72245        end 
    73       # postgres-pr raises a NoMethodError when querying if no conn is available 
     246      # postgres-pr raises a NoMethodError when querying if no connection is available. 
    74247      rescue PGError, NoMethodError 
    75248        false 
    76249      end 
    77250 
    78251      # Close then reopen the connection. 
    79252      def reconnect! 
    80         # TODO: postgres-pr doesn't have PGconn#reset. 
    81253        if @connection.respond_to?(:reset) 
    82254          @connection.reset 
    83255          configure_connection 
     256        else 
     257          disconnect! 
     258          connect 
    84259        end 
    85260      end 
    86261 
     262      # Close the connection. 
    87263      def disconnect! 
    88         # Both postgres and postgres-pr respond to :close 
    89264        @connection.close rescue nil 
    90265      end 
    91266 
    92       def native_database_types 
     267      def native_database_types #:nodoc: 
    93268        { 
    94269          :primary_key => "serial primary key", 
    95270          :string      => { :name => "character varying", :limit => 255 }, 
     
    106281        } 
    107282      end 
    108283 
     284      # Does PostgreSQL support migrations? 
    109285      def supports_migrations? 
    110286        true 
    111287      end 
    112288 
     289      # Does PostgreSQL support standard conforming strings? 
     290      def supports_standard_conforming_strings? 
     291        # Temporarily set the client message level above error to prevent unintentional 
     292        # error messages in the logs when working on a PostgreSQL database server that 
     293        # does not support standard conforming strings. 
     294        client_min_messages_old = client_min_messages 
     295        self.client_min_messages = 'panic' 
     296 
     297        # postgres-pr does not raise an exception when client_min_messages is set higher 
     298        # than error and "SHOW standard_conforming_strings" fails, but returns an empty 
     299        # PGresult instead. 
     300        has_support = execute('SHOW standard_conforming_strings')[0][0] rescue false 
     301        self.client_min_messages = client_min_messages_old 
     302        has_support 
     303      end 
     304 
     305      # Returns the configured supported identifier length supported by PostgreSQL, 
     306      # or report the default of 63 on PostgreSQL 7.x. 
    113307      def table_alias_length 
    114         63 
     308        @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63) 
    115309      end 
    116310 
    117311      # QUOTING ================================================== 
    118312 
    119       def quote(value, column = nil) 
     313      # Quotes PostgreSQL-specific data types for SQL input. 
     314      def quote(value, column = nil) #:nodoc: 
    120315        if value.kind_of?(String) && column && column.type == :binary 
    121           "'#{escape_bytea(value)}'" 
     316          "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'" 
     317        elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ 
     318          "xml '#{quote_string(value)}'" 
     319        elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ 
     320          # Not truly string input, so doesn't require (or allow) escape string syntax. 
     321          "'#{value.to_s}'" 
     322        elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/ 
     323          case value 
     324            when /^[01]*$/ 
     325              "B'#{value}'" # Bit-string notation 
     326            when /^[0-9A-F]*$/i 
     327              "X'#{value}'" # Hexadecimal notation 
     328          end 
    122329        else 
    123330          super 
    124331        end 
    125332      end 
    126333 
    127       def quote_column_name(name) 
     334      # Quotes strings for use in SQL input in the postgres driver for better performance. 
     335      def quote_string(s) #:nodoc: 
     336        if PGconn.respond_to?(:escape) 
     337          self.class.instance_eval do 
     338            define_method(:quote_string) do |s| 
     339              PGconn.escape(s) 
     340            end 
     341          end 
     342        else 
     343          # There are some incorrectly compiled postgres drivers out there 
     344          # that don't define PGconn.escape. 
     345          self.class.instance_eval do 
     346            undef_method(:quote_string) 
     347          end 
     348        end 
     349        quote_string(s) 
     350      end 
     351 
     352      # Quotes column names for use in SQL queries. 
     353      def quote_column_name(name) #:nodoc: 
    128354        %("#{name}") 
    129355      end 
    130356 
    131       # Include microseconds if the value is a Time responding to usec. 
    132       def quoted_date(value) 
     357      # Quote date/time values for use in SQL input. Includes microseconds 
     358      # if the value is a Time responding to usec. 
     359      def quoted_date(value) #:nodoc: 
    133360        if value.acts_like?(:time) && value.respond_to?(:usec) 
    134361          "#{super}.#{sprintf("%06d", value.usec)}" 
    135362        else 
     
    140367 
    141368      # DATABASE STATEMENTS ====================================== 
    142369 
    143       def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 
     370      # Executes an INSERT query and returns the new record's ID 
     371      def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)         
    144372        execute(sql, name) 
    145373        table = sql.split(" ", 4)[2] 
    146374        id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) 
    147375      end 
    148376 
     377      # Queries the database and returns the results in an Array or nil otherwise. 
    149378      def query(sql, name = nil) #:nodoc: 
    150379        log(sql, name) do 
    151380          if @async 
     
    156385        end 
    157386      end 
    158387 
    159       def execute(sql, name = nil) #:nodoc: 
     388      # Executes a SQL statement, returning a PGresult object on success 
     389      # or raising a PGError exception otherwise. 
     390      def execute(sql, name = nil) 
    160391        log(sql, name) do 
    161392          if @async 
    162393            @connection.async_exec(sql) 
     
    166397        end 
    167398      end 
    168399 
    169       def update(sql, name = nil) #:nodoc: 
     400      # Executes an UPDATE query and returns the number of affected tuples. 
     401      def update(sql, name = nil) 
    170402        execute(sql, name).cmdtuples 
    171403      end 
    172404 
    173       def begin_db_transaction #:nodoc: 
     405      # Begins a transaction. 
     406      def begin_db_transaction 
    174407        execute "BEGIN" 
    175408      end 
    176409 
    177       def commit_db_transaction #:nodoc: 
     410      # Commits a transaction. 
     411      def commit_db_transaction 
    178412        execute "COMMIT" 
    179413      end 
    180414 
    181       def rollback_db_transaction #:nodoc: 
     415      # Aborts a transaction. 
     416      def rollback_db_transaction 
    182417        execute "ROLLBACK" 
    183418      end 
    184419 
    185420      # SCHEMA STATEMENTS ======================================== 
    186421 
    187       # Return the list of all tables in the schema search path
    188       def tables(name = nil) #:nodoc: 
     422      # Returns the list of all tables in the schema search path or a specified schema
     423      def tables(name = nil) 
    189424        schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') 
    190425        query(<<-SQL, name).map { |row| row[0] } 
    191426          SELECT tablename 
     
    194429        SQL 
    195430      end 
    196431 
    197       def indexes(table_name, name = nil) #:nodoc: 
     432      # Returns the list of all indexes for a table. 
     433      def indexes(table_name, name = nil) 
    198434        result = query(<<-SQL, name) 
    199435          SELECT i.relname, d.indisunique, a.attname 
    200436            FROM pg_class t, pg_class i, pg_index d, pg_attribute a 
     
    227463        indexes 
    228464      end 
    229465 
    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") 
     466      # Returns the list of all column definitions for a table. 
     467      def columns(table_name, name = nil) 
     468        # Limit, precision, and scale are all handled by superclass. 
     469        column_definitions(table_name).collect do |name, type, default, notnull| 
     470          PostgreSQLColumn.new(name, default, type, notnull == 'f') 
    234471        end 
    235472      end 
    236473 
    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: 
     474      # Sets the schema search path to a string of comma-separated schema names. 
     475      # Names beginning with $ have to be quoted (e.g. $user => '$user'). 
     476      # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html 
     477      # 
     478      # This should be not be called manually but set in database.yml. 
     479      def schema_search_path=(schema_csv) 
    241480        if schema_csv 
    242481          execute "SET search_path TO #{schema_csv}" 
    243           @schema_search_path = nil 
     482          @schema_search_path = schema_csv 
    244483        end 
    245484      end 
    246485 
    247       def schema_search_path #:nodoc: 
     486      # Returns the active schema search path. 
     487      def schema_search_path 
    248488        @schema_search_path ||= query('SHOW search_path')[0][0] 
    249489      end 
    250490 
    251       def default_sequence_name(table_name, pk = nil) 
     491      # Returns the current client message level. 
     492      def client_min_messages 
     493        query('SHOW client_min_messages')[0][0] 
     494      end 
     495 
     496      # Set the client message level. 
     497      def client_min_messages=(level) 
     498        execute("SET client_min_messages TO '#{level}'") 
     499      end 
     500 
     501      # Returns the sequence name for a table's primary key or some other specified key. 
     502      def default_sequence_name(table_name, pk = nil) #:nodoc: 
    252503        default_pk, default_seq = pk_and_sequence_for(table_name) 
    253504        default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq" 
    254505      end 
    255506 
    256       # Resets sequence to the max value of the table's pk if present
    257       def reset_pk_sequence!(table, pk = nil, sequence = nil) 
     507      # Resets the sequence of a table's primary key to the maximum value
     508      def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: 
    258509        unless pk and sequence 
    259510          default_pk, default_sequence = pk_and_sequence_for(table) 
    260511          pk ||= default_pk 
     
    271522        end 
    272523      end 
    273524 
    274       # Find a table's primary key and sequence. 
    275       def pk_and_sequence_for(table) 
     525      # Returns a table's primary key and belonging sequence. 
     526      def pk_and_sequence_for(table) #:nodoc: 
    276527        # First try looking for a sequence with a dependency on the 
    277528        # given table's primary key. 
    278529        result = query(<<-end_sql, 'PK and serial sequence')[0] 
    279           SELECT attr.attname, name.nspname, seq.relname 
     530          SELECT attr.attname, seq.relname 
    280531          FROM pg_class      seq, 
    281532               pg_attribute  attr, 
    282533               pg_depend     dep, 
    283534               pg_namespace  name, 
    284535               pg_constraint cons 
    285536          WHERE seq.oid           = dep.objid 
    286             AND seq.relnamespace  = name.oid 
    287537            AND seq.relkind       = 'S' 
    288538            AND attr.attrelid     = dep.refobjid 
    289539            AND attr.attnum       = dep.refobjsubid 
     
    297547          # If that fails, try parsing the primary key's default value. 
    298548          # Support the 7.x and 8.0 nextval('foo'::text) as well as 
    299549          # the 8.1+ nextval('foo'::regclass). 
    300           # TODO: assumes sequence is in same schema as table. 
    301550          result = query(<<-end_sql, 'PK and custom sequence')[0] 
    302             SELECT attr.attname, name.nspname, split_part(def.adsrc, '''', 2) 
     551            SELECT attr.attname, split_part(def.adsrc, '''', 2) 
    303552            FROM pg_class       t 
    304             JOIN pg_namespace   name ON (t.relnamespace = name.oid) 
    305553            JOIN pg_attribute   attr ON (t.oid = attrelid) 
    306554            JOIN pg_attrdef     def  ON (adrelid = attrelid AND adnum = attnum) 
    307555            JOIN pg_constraint  cons ON (conrelid = adrelid AND adnum = conkey[1]) 
     
    310558              AND def.adsrc ~* 'nextval' 
    311559          end_sql 
    312560        end 
    313         # check for existence of . in sequence name as in public.foo_sequence.  if it does not exist, return unqualified sequence 
    314         # We cannot qualify unqualified sequences, as rails doesn't qualify any table access, using the search path 
     561        # [primary_key, sequence] 
    315562        [result.first, result.last] 
    316563      rescue 
    317564        nil 
    318565      end 
    319566 
     567      # Renames a table. 
    320568      def rename_table(name, new_name) 
    321569        execute "ALTER TABLE #{name} RENAME TO #{new_name}" 
    322570      end 
    323571 
     572      # Adds a column to a table. 
    324573      def add_column(table_name, column_name, type, options = {}) 
    325574        default = options[:default] 
    326575        notnull = options[:null] == false 
     
    343592        end 
    344593      end 
    345594 
    346       def change_column(table_name, column_name, type, options = {}) #:nodoc: 
     595      # Changes the column of a table. 
     596      def change_column(table_name, column_name, type, options = {}) 
    347597        begin 
    348598          execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" 
    349599        rescue ActiveRecord::StatementInvalid 
    350           # This is PG7, so we use a more arcane way of doing it. 
     600          # This is PostgreSQL 7.x, so we have to use a more arcane way of doing it. 
    351601          begin_db_transaction 
    352602          tmp_column_name = "#{column_name}_ar_tmp" 
    353603          add_column(table_name, tmp_column_name, type, options) 
     
    362612        end 
    363613      end 
    364614 
    365       def change_column_default(table_name, column_name, default) #:nodoc: 
     615      # Changes the default value of a table column. 
     616      def change_column_default(table_name, column_name, default) 
    366617        execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}" 
    367618      end 
    368619 
    369       def rename_column(table_name, column_name, new_column_name) #:nodoc: 
     620      # Renames a column in a table. 
     621      def rename_column(table_name, column_name, new_column_name) 
    370622        execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" 
    371623      end 
    372624 
    373       def remove_index(table_name, options) #:nodoc: 
     625      # Drops an index from a table. 
     626      def remove_index(table_name, options = {}) 
    374627        execute "DROP INDEX #{index_name(table_name, options)}" 
    375628      end 
    376629 
    377       def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: 
     630      # Maps logical Rails types to PostgreSQL-specific data types. 
     631      def type_to_sql(type, limit = nil, precision = nil, scale = nil) 
    378632        return super unless type.to_s == 'integer' 
    379633 
    380634        if limit.nil? || limit == 4 
     
    386640        end 
    387641      end 
    388642       
    389       # SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause. 
     643      # Returns a SELECT DISTINCT clause for a given set of columns and a given ORDER BY clause. 
    390644      # 
    391645      # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and 
    392646      # requires that the ORDER BY include the distinct column. 
    393647      # 
    394648      #   distinct("posts.id", "posts.created_at desc") 
    395       def distinct(columns, order_by) 
     649      def distinct(columns, order_by) #:nodoc: 
    396650        return "DISTINCT #{columns}" if order_by.blank? 
    397651 
    398         # construct a clean list of column names from the ORDER BY clause, removing 
    399         # any asc/desc modifiers 
     652        # Construct a clean list of column names from the ORDER BY clause, removing 
     653        # any ASC/DESC modifiers 
    400654        order_columns = order_by.split(',').collect { |s| s.split.first } 
    401655        order_columns.delete_if &:blank? 
    402656        order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } 
    403657 
    404         # return a DISTINCT ON() clause that's distinct on the columns we want but includes 
    405         # all the required columns for the ORDER BY to work properly 
     658        # Return a DISTINCT ON() clause that's distinct on the columns we want but includes 
     659        # all the required columns for the ORDER BY to work properly. 
    406660        sql = "DISTINCT ON (#{columns}) #{columns}, " 
    407661        sql << order_columns * ', ' 
    408662      end 
    409663       
    410       # ORDER BY clause for the passed order option. 
     664      # Returns a ORDER BY clause for the passed order option. 
    411665      #  
    412666      # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this 
    413667      # by wrapping the sql as a sub-select and ordering in that query. 
    414       def add_order_by_for_association_limiting!(sql, options) 
     668      def add_order_by_for_association_limiting!(sql, options) #:nodoc: 
    415669        return sql if options[:order].blank? 
    416670