Changeset 7329
- Timestamp:
- 08/16/07 06:26:30 (1 year ago)
- Files:
-
- trunk/activerecord/CHANGELOG (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb (modified) (3 diffs)
- trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb (modified) (28 diffs)
- trunk/activerecord/test/datatype_test_postgresql.rb (modified) (1 diff)
- trunk/activerecord/test/finder_test.rb (modified) (3 diffs)
- trunk/activerecord/test/fixtures/db_definitions/postgresql.drop.sql (modified) (1 diff)
- trunk/activerecord/test/fixtures/db_definitions/postgresql.sql (modified) (1 diff)
- trunk/railties/configs/databases/postgresql.yml (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/CHANGELOG
r7326 r7329 1 1 *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 2 6 3 7 * Don't clobber includes passed to has_many.count [danger] trunk/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
r6061 r7329 12 12 value = value.to_s 13 13 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) 15 15 elsif column && [:integer, :float].include?(column.type) 16 16 value = column.type == :integer ? value.to_i : value.to_f 17 17 value.to_s 18 18 else 19 " '#{quote_string(value)}'" # ' (for ruby-mode)19 "#{quoted_string_prefix}'#{quote_string(value)}'" # ' (for ruby-mode) 20 20 end 21 21 when NilClass then "NULL" … … 29 29 "'#{quoted_date(value)}'" 30 30 else 31 " '#{quote_string(value.to_yaml)}'"31 "#{quoted_string_prefix}'#{quote_string(value.to_yaml)}'" 32 32 end 33 33 end … … 57 57 value.to_s(:db) 58 58 end 59 60 def quoted_string_prefix 61 '' 62 end 59 63 end 60 64 end trunk/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
r6891 r7329 13 13 password = config[:password].to_s 14 14 15 min_messages = config[:min_messages]16 17 15 if config.has_key?(:database) 18 16 database = config[:database] … … 21 19 end 22 20 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) 32 24 end 33 25 end 34 26 35 27 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. 38 211 # 39 212 # Options: … … 49 222 # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. 50 223 class PostgreSQLAdapter < AbstractAdapter 224 # Returns 'PostgreSQL' as adapter name for identification purposes. 51 225 def adapter_name 52 226 'PostgreSQL' 53 227 end 54 228 55 def initialize(connection, logger, config = {}) 229 # Initializes and connects a PostgreSQL adapter. 230 def initialize(connection, logger, connection_parameters, config) 56 231 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 63 235 end 64 236 … … 68 240 @connection.status == PGconn::CONNECTION_OK 69 241 else 242 # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query 70 243 @connection.query 'SELECT 1' 71 244 true 72 245 end 73 # postgres-pr raises a NoMethodError when querying if no conn is available246 # postgres-pr raises a NoMethodError when querying if no connection is available. 74 247 rescue PGError, NoMethodError 75 248 false … … 78 251 # Close then reopen the connection. 79 252 def reconnect! 80 # TODO: postgres-pr doesn't have PGconn#reset.81 253 if @connection.respond_to?(:reset) 82 254 @connection.reset 83 255 configure_connection 84 end 85 end 86 256 else 257 disconnect! 258 connect 259 end 260 end 261 262 # Close the connection. 87 263 def disconnect! 88 # Both postgres and postgres-pr respond to :close89 264 @connection.close rescue nil 90 265 end 91 266 92 def native_database_types 267 def native_database_types #:nodoc: 93 268 { 94 269 :primary_key => "serial primary key", … … 107 282 end 108 283 284 # Does PostgreSQL support migrations? 109 285 def supports_migrations? 110 286 true 111 287 end 112 288 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. 113 307 def table_alias_length 114 63308 @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63) 115 309 end 116 310 117 311 # QUOTING ================================================== 118 312 119 def quote(value, column = nil) 313 # Quotes PostgreSQL-specific data types for SQL input. 314 def quote(value, column = nil) #:nodoc: 120 315 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 122 329 else 123 330 super … … 125 332 end 126 333 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: 128 354 %("#{name}") 129 355 end 130 356 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: 133 360 if value.acts_like?(:time) && value.respond_to?(:usec) 134 361 "#{super}.#{sprintf("%06d", value.usec)}" … … 141 368 # DATABASE STATEMENTS ====================================== 142 369 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) 144 372 execute(sql, name) 145 373 table = sql.split(" ", 4)[2] … … 147 375 end 148 376 377 # Queries the database and returns the results in an Array or nil otherwise. 149 378 def query(sql, name = nil) #:nodoc: 150 379 log(sql, name) do … … 157 386 end 158 387 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) 160 391 log(sql, name) do 161 392 if @async … … 167 398 end 168 399 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) 170 402 execute(sql, name).cmdtuples 171 403 end 172 404 173 def begin_db_transaction #:nodoc: 405 # Begins a transaction. 406 def begin_db_transaction 174 407 execute "BEGIN" 175 408 end 176 409 177 def commit_db_transaction #:nodoc: 410 # Commits a transaction. 411 def commit_db_transaction 178 412 execute "COMMIT" 179 413 end 180 414 181 def rollback_db_transaction #:nodoc: 415 # Aborts a transaction. 416 def rollback_db_transaction 182 417 execute "ROLLBACK" 183 418 end … … 185 420 # SCHEMA STATEMENTS ======================================== 186 421 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) 189 424 schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') 190 425 query(<<-SQL, name).map { |row| row[0] } … … 195 430 end 196 431 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) 198 434 result = query(<<-SQL, name) 199 435 SELECT i.relname, d.indisunique, a.attname … … 228 464 end 229 465 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) 241 480 if schema_csv 242 481 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 248 488 @schema_search_path ||= query('SHOW search_path')[0][0] 249 489 end 250 490 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: 252 503 default_pk, default_seq = pk_and_sequence_for(table_name) 253 504 default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq" 254 505 end 255 506 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: 258 509 unless pk and sequence 259 510 default_pk, default_sequence = pk_and_sequence_for(table) … … 272 523 end 273 524 274 # Find a table's primary key andsequence.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: 276 527 # First try looking for a sequence with a dependency on the 277 528 # given table's primary key. 278 529 result = query(<<-end_sql, 'PK and serial sequence')[0] 279 SELECT attr.attname, name.nspname,seq.relname530 SELECT attr.attname, seq.relname 280 531 FROM pg_class seq, 281 532 pg_attribute attr, … … 284 535 pg_constraint cons 285 536 WHERE seq.oid = dep.objid 286 AND seq.relnamespace = name.oid287 537 AND seq.relkind = 'S' 288 538 AND attr.attrelid = dep.refobjid … … 298 548 # Support the 7.x and 8.0 nextval('foo'::text) as well as 299 549 # the 8.1+ nextval('foo'::regclass). 300 # TODO: assumes sequence is in same schema as table.301 550 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) 303 552 FROM pg_class t 304 JOIN pg_namespace name ON (t.relnamespace = name.oid)305 553 JOIN pg_attribute attr ON (t.oid = attrelid) 306 554 JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum) … … 311 559 end_sql 312 560 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] 315 562 [result.first, result.last] 316 563 rescue … … 318 565 end 319 566 567 # Renames a table. 320 568 def rename_table(name, new_name) 321 569 execute "ALTER TABLE #{name} RENAME TO #{new_name}" 322 570 end 323 571 572 # Adds a column to a table. 324 573 def add_column(table_name, column_name, type, options = {}) 325 574 default = options[:default] … … 344 593 end 345 594 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 = {}) 347 597 begin 348 598 execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" 349 599 rescue ActiveRecord::StatementInvalid 350 # This is P G7, so weuse 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. 351 601 begin_db_transaction 352 602 tmp_column_name = "#{column_name}_ar_tmp" … … 363 613 end 364 614 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) 366 617 execute "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}" 367 618 end 368 619 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) 370 622 execute "ALTER TABLE #{table_name} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" 371 623 end 372 624 373 def remove_index(table_name, options) #:nodoc: 625 # Drops an index from a table. 626 def remove_index(table_name, options = {}) 374 627 execute "DROP INDEX #{index_name(table_name, options)}" 375 628 end 376 629 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) 378 632 return super unless type.to_s == 'integer' 379 633 … … 387 641 end 388 642 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. 390 644 # 391 645 # PostgreSQL requires the ORDER BY columns in the select list for distinct queries, and … … 393 647 # 394 648 # distinct("posts.id", "posts.created_at desc") 395 def distinct(columns, order_by) 649 def distinct(columns, order_by) #:nodoc: 396 650 return "DISTINCT #{columns}" if order_by.blank? 397 651 398 # construct a clean list of column names from the ORDER BY clause, removing399 # any asc/descmodifiers652 # Construct a clean list of column names from the ORDER BY clause, removing 653 # any ASC/DESC modifiers 400 654 order_columns = order_by.split(',').collect { |s| s.split.first } 401 655 order_columns.delete_if &:blank? 402 656 order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" } 403 657 404 # return a DISTINCT ON() clause that's distinct on the columns we want but includes405 # 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. 406 660 sql = "DISTINCT ON (#{columns}) #{columns}, " 407 661 sql << order_columns * ', ' 408 662 end 409 663 410 # ORDER BY clause for the passed order option.664 # Returns a ORDER BY clause for the passed order option. 411 665 # 412 666 # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this 413 667 # 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: 415 669 return sql if options[:order].blank? 416 670 … … 422 676 end 423 677 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 424 695 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. 430 738 def configure_connection 431 739 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: 440 752 Integer(select_value("SELECT currval('#{sequence_name}')")) 441 753 end 442 754 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. 443 757 def select(sql, name = nil) 444 758 res = execute(sql, name) … … 449 763 results.each do |row| 450 764 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 459 783 end 460 784 461 hashed_row[fields[cel _index]] = column785 hashed_row[fields[cell_index]] = column 462 786 end 463 787 rows << hashed_row … … 468 792 end 469 793 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. 519 795 # 520 796 # The underlying query is roughly: … … 534 810 # - format_type includes the column size constraint, e.g. varchar(50) 535 811 # - ::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: 537 813 query <<-end_sql 538 814 SELECT a.attname, format_type(a.atttypid, a.atttypmod), d.adsrc, a.attnotnull … … 544 820 end_sql 545 821 end 546 547 # Translate PostgreSQL-specific types into simplified SQL types.548 # These are special cases; standard types are handled by549 # 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_type553 # 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 end563 end564 565 def default_value(value)566 # Boolean types567 return "t" if value =~ /true/i568 return "f" if value =~ /false/i569 570 # Char/String/Bytea type values571 return $1 if value =~ /^'(.*)'::(bpchar|text|character varying|bytea)$/572 573 # Numeric values574 return value if value =~ /^-?[0-9]+(\.[0-9]*)?/575 576 # Fixed dates / times577 return $1 if value =~ /^'(.+)'::(date|timestamp)/578 579 # Anything else is blank, some user type, or some function580 # and we can't know the value of that, so return nil.581 return nil582 end583 822 end 584 823 end trunk/activerecord/test/datatype_test_postgresql.rb
r4605 r7329 1 1 require 'abstract_unit' 2 2 3 class PostgresqlDatatype < ActiveRecord::Base 4 end 5 6 class PGDataTypeTest < Test::Unit::TestCase 3 class PostgresqlArray < ActiveRecord::Base 4 end 5 6 class PostgresqlMoney < ActiveRecord::Base 7 end 8 9 class PostgresqlNumber < ActiveRecord::Base 10 end 11 12 class PostgresqlTime < ActiveRecord::Base 13 end 14 15 class PostgresqlNetworkAddress < ActiveRecord::Base 16 end 17 18 class PostgresqlBitString < ActiveRecord::Base 19 end 20 21 class PostgresqlOid < ActiveRecord::Base 22 end 23 24 class PostgresqlDataTypeTest < Test::Unit::TestCase 7 25 self.use_transactional_fixtures = false 8