Ticket #8049: functionally_improve_postgresql_adapter.diff
| File functionally_improve_postgresql_adapter.diff, 49.8 kB (added by roderickvd, 1 year ago) |
|---|
-
activerecord/test/datatype_test_postgresql.rb
old new 1 1 require 'abstract_unit' 2 2 3 class Postgresql Datatype< ActiveRecord::Base3 class PostgresqlArray < ActiveRecord::Base 4 4 end 5 5 6 class PGDataTypeTest < Test::Unit::TestCase 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 26 9 TABLE_NAME = 'postgresql_datatypes'10 COLUMNS = [11 'id SERIAL PRIMARY KEY',12 'commission_by_quarter INTEGER[]',13 'nicknames TEXT[]'14 ]15 16 27 def setup 17 28 @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 end22 29 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) 25 52 end 26 53 27 54 def test_data_type_of_array_types 28 assert_equal :string, @first .column_for_attribute("commission_by_quarter").type29 assert_equal :string, @first .column_for_attribute("nicknames").type55 assert_equal :string, @first_array.column_for_attribute(:commission_by_quarter).type 56 assert_equal :string, @first_array.column_for_attribute(:nicknames).type 30 57 end 31 58 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 32 87 def test_array_values 33 assert_equal '{35000,21000,18000,17000}', @first.commission_by_quarter34 assert_equal '{foo,bar,baz}', @first.nicknames88 assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter 89 assert_equal '{foo,bar,baz}', @first_array.nicknames 35 90 end 36 91 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 37 121 def test_update_integer_array 38 122 new_value = '{32800,95000,29350,17000}' 39 123 assert @first.commission_by_quarter = new_value 40 124 assert @first.save 41 125 assert @first.reload 42 126 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 43 131 end 44 132 45 133 def test_update_text_array … … 48 136 assert @first.save 49 137 assert @first.reload 50 138 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 51 143 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 52 203 end -
activerecord/test/finder_test.rb
old new 267 267 end 268 268 269 269 def test_bind_enumerable 270 quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) 271 270 272 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)) 272 274 273 275 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)) # ' 275 277 276 278 require 'set' 277 279 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))) 279 281 280 282 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))) # ' 282 284 end 283 285 284 286 def test_bind_empty_enumerable … … 289 291 end 290 292 291 293 def test_bind_string 292 assert_equal "''", bind('?', '')294 assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '') 293 295 end 294 296 295 297 def test_bind_record … … 301 303 end 302 304 303 305 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") 306 308 end 307 309 308 310 def test_count -
activerecord/test/fixtures/db_definitions/postgresql.sql
old new 245 245 "monkeyID" INTEGER PRIMARY KEY, 246 246 "fleaCount" INTEGER 247 247 ); 248 249 CREATE TABLE postgresql_arrays ( 250 id SERIAL PRIMARY KEY, 251 commission_by_quarter INTEGER[], 252 nicknames TEXT[] 253 ); 254 255 CREATE TABLE postgresql_moneys ( 256 id SERIAL PRIMARY KEY, 257 wealth MONEY 258 ); 259 260 CREATE TABLE postgresql_numbers ( 261 id SERIAL PRIMARY KEY, 262 single REAL, 263 double DOUBLE PRECISION 264 ); 265 266 CREATE TABLE postgresql_times ( 267 SERIAL PRIMARY KEY, 268 time_interval INTERVAL 269 ); 270 271 CREATE TABLE postgresql_network_addresses ( 272 id SERIAL PRIMARY KEY, 273 cidr_address CIDR, 274 inet_address INET, 275 mac_address MACADDR 276 ); 277 278 CREATE TABLE postgresql_bit_strings ( 279 id SERIAL PRIMARY KEY, 280 bit_string BIT(8), 281 bit_string_varying BIT VARYING(8) 282 ); 283 284 CREATE TABLE postgresql_oids ( 285 id SERIAL PRIMARY KEY, 286 obj_id OID 287 ); -
activerecord/test/fixtures/db_definitions/postgresql.drop.sql
old new 35 35 DROP TABLE numeric_data; 36 36 DROP TABLE column_data; 37 37 DROP TABLE mixed_case_monkeys; 38 DROP TABLE postgresql_arrays; 39 DROP TABLE postgresql_moneys; 40 DROP TABLE postgresql_numbers; 41 DROP TABLE postgresql_times; 42 DROP TABLE postgresql_network_addresses; 43 DROP TABLE postgresql_bit_strings; 44 DROP TABLE postgresql_oids; -
activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
old new 11 11 when String, ActiveSupport::Multibyte::Chars 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" 22 22 when TrueClass then (column && column.type == :integer ? '1' : quoted_true) … … 28 28 if value.acts_like?(:date) || value.acts_like?(:time) 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 34 34 end … … 56 56 def quoted_date(value) 57 57 value.to_s(:db) 58 58 end 59 60 def quoted_string_prefix 61 '' 62 end 59 63 end 60 64 end 61 65 end -
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
old new 12 12 username = config[:username].to_s 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] 19 17 else 20 18 raise ArgumentError, "No database specified. Missing argument: database." 21 19 end 22 20 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 26 26 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 28 34 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 30 41 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 32 205 end 33 206 end 34 207 35 208 module ConnectionAdapters 36 # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base37 # (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 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: 40 213 # … … 48 221 # * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection. 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 @con fig =config232 @connection_parameters, @config = connection_parameters, config 58 233 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 63 235 end 64 236 65 237 # Is this connection alive and ready for queries? … … 67 239 if @connection.respond_to?(:status) 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 76 249 end 77 250 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 256 else 257 disconnect! 258 connect 84 259 end 85 260 end 86 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", 95 270 :string => { :name => "character varying", :limit => 255 }, … … 106 281 } 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 124 331 end 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)}" 135 362 else … … 140 367 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] 146 374 id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) 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 151 380 if @async … … 156 385 end 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 162 393 @connection.async_exec(sql) … … 166 397 end 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 184 419 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] } 191 426 SELECT tablename … … 194 429 SQL 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 200 436 FROM pg_class t, pg_class i, pg_index d, pg_attribute a … … 227 463 indexes 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") 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') 234 471 end 235 472 end 236 473 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) 241 480 if schema_csv 242 481 execute "SET search_path TO #{schema_csv}" 243 @schema_search_path = nil482 @schema_search_path = schema_csv 244 483 end 245 484 end 246 485 247 def schema_search_path #:nodoc: 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) 260 511 pk ||= default_pk … … 271 522 end 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, 282 533 pg_depend dep, 283 534 pg_namespace name, 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 289 539 AND attr.attnum = dep.refobjsubid … … 297 547 # If that fails, try parsing the primary key's default value. 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) 307 555 JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) … … 310 558 AND def.adsrc ~* 'nextval' 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 317 564 nil 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] 326 575 notnull = options[:null] == false … … 343 592 end 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" 353 603 add_column(table_name, tmp_column_name, type, options) … … 362 612 end 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 380 634 if limit.nil? || limit == 4 … … 386 640 end 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 392 646 # requires that the ORDER BY include the distinct column. 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