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

Changeset 7329

Show
Ignore:
Timestamp:
08/16/07 06:26:30 (1 year ago)
Author:
nzkoz
Message:

Improve performance and functionality of the postgresql adapter. Closes #8049 [roderickvd]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r7326 r7329  
    11*SVN* 
     2 
     3* Improve performance and functionality of the postgresql adapter.  Closes #8049 [roderickvd] 
     4 
     5        For more information see: http://dev.rubyonrails.org/ticket/8049 
    26 
    37* Don't clobber includes passed to has_many.count [danger] 
  • trunk/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb

    r6061 r7329  
    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" 
     
    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 
     
    5757        value.to_s(:db) 
    5858      end 
     59 
     60      def quoted_string_prefix 
     61        '' 
     62      end 
    5963    end 
    6064  end 
  • trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

    r6891 r7329  
    1313      password = config[:password].to_s 
    1414 
    15       min_messages = config[:min_messages] 
    16  
    1715      if config.has_key?(:database) 
    1816        database = config[:database] 
     
    2119      end 
    2220 
    23       pga = ConnectionAdapters::PostgreSQLAdapter.new( 
    24         PGconn.connect(host, port, "", "", database, username, password), logger, config 
    25       ) 
    26  
    27       PGconn.translate_results = false if PGconn.respond_to? :translate_results= 
    28  
    29       pga.schema_search_path = config[:schema_search_path] || config[:schema_order] 
    30  
    31       pga 
     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) 
    3224    end 
    3325  end 
    3426 
    3527  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. 
     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 
     34 
     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 
     41 
     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 
     205    end 
     206  end 
     207 
     208  module ConnectionAdapters 
     209    # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure 
     210    # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers. 
    38211    # 
    39212    # Options: 
     
    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 
    58  
    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 
     232        @connection_parameters, @config = connection_parameters, config 
     233 
     234        connect 
    63235      end 
    64236 
     
    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 
     
    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 
    84         end 
    85       end 
    86  
     256        else 
     257          disconnect! 
     258          connect 
     259        end 
     260      end 
     261 
     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", 
     
    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 
     
    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)}" 
     
    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] 
     
    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 
     
    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 
     
    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 
     
    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] } 
     
    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 
     
    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") 
    234         end 
    235       end 
    236  
    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: 
     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') 
     471        end 
     472      end 
     473 
     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 
    244         end 
    245       end 
    246  
    247       def schema_search_path #:nodoc: 
     482          @schema_search_path = schema_csv 
     483        end 
     484      end 
     485 
     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) 
     
    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, 
     
    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 
     
    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) 
     
    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 
     
    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] 
     
    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" 
     
    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 
     
    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 
     
    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         
     
    422676      end 
    423677 
     678      protected 
     679        # Returns the version of the connected PostgreSQL version. 
     680        def postgresql_version 
     681          @postgresql_version ||= 
     682            if @connection.respond_to?(:server_version) 
     683              @connection.server_version 
     684            else 
     685              # Mimic PGconn.server_version behavior 
     686              begin 
     687                query('SELECT version()')[0][0] =~ /PostgreSQL (\d+)\.(\d+)\.(\d+)/ 
     688                ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i 
     689              rescue 
     690                0 
     691              end 
     692            end 
     693        end 
     694 
    424695      private 
    425         BYTEA_COLUMN_TYPE_OID = 17 
    426         NUMERIC_COLUMN_TYPE_OID = 1700 
    427         TIMESTAMPOID = 1114 
    428         TIMESTAMPTZOID = 1184 
    429  
     696        # The internal PostgreSQL identifer of the money data type. 
     697        MONEY_COLUMN_TYPE_OID = 790 #:nodoc: 
     698 
     699        # Connects to a PostgreSQL server and sets up the adapter depending on the 
     700        # connected server's characteristics. 
     701        def connect 
     702          @connection = PGconn.connect(*@connection_parameters) 
     703          PGconn.translate_results = false if PGconn.respond_to?(:translate_results=) 
     704 
     705          # Ignore async_exec and async_query when using postgres-pr. 
     706          @async = @config[:allow_concurrency] && @connection.respond_to?(:async_exec) 
     707 
     708          # Use escape string syntax if available. We cannot do this lazily when encountering 
     709          # the first string, because that could then break any transactions in progress. 
     710          # See: http://www.postgresql.org/docs/current/static/runtime-config-compatible.html 
     711          # If PostgreSQL doesn't know the standard_conforming_strings parameter then it doesn't 
     712          # support escape string syntax. Don't override the inherited quoted_string_prefix. 
     713          if supports_standard_conforming_strings? 
     714            self.class.instance_eval do 
     715              define_method(:quoted_string_prefix) { 'E' } 
     716            end 
     717          end 
     718 
     719          # Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of 
     720          # PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision 
     721          # should know about this but can't detect it there, so deal with it here. 
     722          money_precision = (postgresql_version >= 80300) ? 19 : 10 
     723          PostgreSQLColumn.module_eval(<<-end_eval) 
     724            def extract_precision(sql_type) 
     725              if sql_type =~ /^money$/ 
     726                #{money_precision} 
     727              else 
     728                super 
     729              end 
     730            end 
     731          end_eval 
     732 
     733          configure_connection 
     734        end 
     735 
     736        # Configures the encoding, verbosity, and schema search path of the connection. 
     737        # This is called by #connect and should not be called manually. 
    430738        def configure_connection 
    431739          if @config[:encoding] 
    432             execute("SET client_encoding TO '#{@config[:encoding]}'") 
    433           end 
    434           if @config[:min_messages] 
    435             execute("SET client_min_messages TO '#{@config[:min_messages]}'") 
    436           end 
    437         end 
    438  
    439         def last_insert_id(table, sequence_name) 
     740            if @connection.respond_to?(:set_client_encoding) 
     741              @connection.set_client_encoding(@config[:encoding]) 
     742            else 
     743              execute("SET client_encoding TO '#{@config[:encoding]}'") 
     744            end 
     745          end 
     746          self.client_min_messages = @config[:min_messages] if @config[:min_messages] 
     747          self.schema_search_path = @config[:schema_search_path] || @config[:schema_order] 
     748        end 
     749 
     750        # Returns the current ID of a table's sequence. 
     751        def last_insert_id(table, sequence_name) #:nodoc: 
    440752          Integer(select_value("SELECT currval('#{sequence_name}')")) 
    441753        end 
    442754 
     755        # Executes a SELECT query and returns the results, performing any data type 
     756        # conversions that require to be performed here instead of in PostgreSQLColumn. 
    443757        def select(sql, name = nil) 
    444758          res = execute(sql, name) 
     
    449763            results.each do |row| 
    450764              hashed_row = {} 
    451               row.each_index do |cel_index| 
    452                 column = row[cel_index] 
    453  
    454                 case res.type(cel_index) 
    455                   when BYTEA_COLUMN_TYPE_OID 
    456                     column = unescape_bytea(column) 
    457                   when NUMERIC_COLUMN_TYPE_OID 
    458                     column = column.to_d if column.respond_to?(:to_d) 
     765              row.each_index do |cell_index| 
     766                column = row[cell_index] 
     767 
     768                # If this is a money type column and there are any currency symbols, 
     769                # then strip them off. Indeed it would be prettier to do this in 
     770                # PostgresSQLColumn.string_to_decimal but would break form input 
     771                # fields that call value_before_type_cast. 
     772                if res.type(cell_index) == MONEY_COLUMN_TYPE_OID 
     773                  # Because money output is formatted according to the locale, there are two 
     774                  # cases to consider (note the decimal seperators): 
     775                  #  (1) $12,345,678.12         
     776                  #  (2) $12.345.678,12 
     777                  case column 
     778                    when /^-?\D+[\d,]+\.\d{2}$/  # (1) 
     779                      column = column.gsub(/[^-\d\.]/, '') 
     780                    when /^-?\D+[\d\.]+,\d{2}$/  # (2) 
     781                      column = column.gsub(/[^-\d,]/, '').sub(/,/, '.') 
     782                  end 
    459783                end 
    460784 
    461                 hashed_row[fields[cel_index]] = column 
     785                hashed_row[fields[cell_index]] = column 
    462786              end 
    463787              rows << hashed_row 
     
    468792        end 
    469793 
    470         def escape_bytea(s) 
    471           if PGconn.respond_to? :escape_bytea 
    472             self.class.send(:define_method, :escape_bytea) do |s| 
    473               PGconn.escape_bytea(s) if s 
    474             end 
    475           else 
    476             self.class.send(:define_method, :escape_bytea) do |s| 
    477               if s 
    478                 result = '' 
    479                 s.each_byte { |c| result << sprintf('\\\\%03o', c) } 
    480                 result 
    481               end 
    482             end 
    483           end 
    484           escape_bytea(s) 
    485         end 
    486  
    487         def unescape_bytea(s) 
    488           if PGconn.respond_to? :unescape_bytea 
    489             self.class.send(:define_method, :unescape_bytea) do |s| 
    490               PGconn.unescape_bytea(s) if s 
    491             end 
    492           else 
    493             self.class.send(:define_method, :unescape_bytea) do |s| 
    494               if s 
    495                 result = '' 
    496                 i, max = 0, s.size 
    497                 while i < max 
    498                   char = s[i] 
    499                   if char == ?\\ 
    500                     if s[i+1] == ?\\ 
    501                       char = ?\\ 
    502                       i += 1 
    503                     else 
    504                       char = s[i+1..i+3].oct 
    505                       i += 3 
    506                     end 
    507                   end 
    508                   result << char 
    509                   i += 1 
    510                 end 
    511                 result 
    512               end 
    513             end 
    514           end 
    515           unescape_bytea(s) 
    516         end 
    517  
    518         # Query a table's column names, default values, and types. 
     794        # Returns the list of a table's column names, data types, and default values. 
    519795        # 
    520796        # The underlying query is roughly: 
     
    534810        #  - format_type includes the column size constraint, e.g. varchar(50) 
    535811        #  - ::regclass is a function that gives the id for a table name 
    536         def column_definitions(table_name) 
     812        def column_definitions(table_name) #:nodoc: 
    537813          query <<-end_sql 
    538814            SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull 
     
    544820          end_sql 
    545821        end 
    546  
    547         # Translate PostgreSQL-specific types into simplified SQL types. 
    548         # These are special cases; standard types are handled by 
    549         # ConnectionAdapters::Column#simplified_type. 
    550         def translate_field_type(field_type) 
    551           # Match the beginning of field_type since it may have a size constraint on the end. 
    552           case field_type 
    553             # PostgreSQL array data types. 
    554             when /\[\]$/i  then 'string' 
    555             when /^timestamp/i    then 'datetime' 
    556             when /^real|^money/i  then 'float' 
    557             when /^interval/i     then 'string' 
    558             # geometric types (the line type is currently not implemented in postgresql) 
    559             when /^(?:point|lseg|box|"?path"?|polygon|circle)/i  then 'string' 
    560             when /^bytea/i        then 'binary' 
    561             else field_type       # Pass through standard types. 
    562           end 
    563         end 
    564  
    565         def default_value(value) 
    566           # Boolean types 
    567           return "t" if value =~ /true/i 
    568           return "f" if value =~ /false/i 
    569  
    570           # Char/String/Bytea type values 
    571           return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/ 
    572  
    573           # Numeric values 
    574           return value if value =~ /^-?[0-9]+(\.[0-9]*)?/ 
    575  
    576           # Fixed dates / times 
    577           return $1 if value =~ /^'(.+)'::(date|timestamp)/ 
    578  
    579           # Anything else is blank, some user type, or some function 
    580           # and we can't know the value of that, so return nil. 
    581           return nil 
    582         end 
    583822    end 
    584823  end 
  • trunk/activerecord/test/datatype_test_postgresql.rb

    r4605 r7329  
    11require 'abstract_unit' 
    22 
    3 class PostgresqlDatatype < ActiveRecord::Base 
    4 end 
    5  
    6 class PGDataTypeTest < Test::Unit::TestCase 
     3class PostgresqlArray < ActiveRecord::Base 
     4end 
     5 
     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 
    8