Ticket #8049: improve_postgresql_adapter.diff
| File improve_postgresql_adapter.diff, 58.5 kB (added by roderickvd, 2 years ago) |
|---|
-
activerecord/test/migration_test.rb
old new 194 194 Person.connection.create_table :testings do |t| 195 195 t.column :foo, :string 196 196 end 197 198 con = Person.connection 197 198 con = Person.connection 199 199 Person.connection.enable_identity_insert("testings", true) if current_adapter?(:SybaseAdapter) 200 200 Person.connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" 201 201 Person.connection.enable_identity_insert("testings", false) if current_adapter?(:SybaseAdapter) … … 278 278 279 279 # Test for 30 significent digits (beyond the 16 of float), 10 of them 280 280 # after the decimal place. 281 281 282 282 assert_equal BigDecimal.new("0012345678901234567890.0123456789"), bob.wealth 283 283 284 284 assert_equal true, bob.male? … … 405 405 t.column :url, :string 406 406 end 407 407 ActiveRecord::Base.connection.add_index :octopuses, :url 408 408 409 409 ActiveRecord::Base.connection.rename_table :octopuses, :octopi 410 410 411 411 # Using explicit id in insert for compatibility across all databases … … 436 436 old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") 437 437 assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } 438 438 assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } 439 new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") 439 new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") 440 440 assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } 441 441 assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } 442 442 assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } 443 443 end 444 444 445 445 def test_change_column_with_nil_default 446 446 Person.connection.add_column "people", "contributor", :boolean, :default => true 447 447 Person.reset_column_information 448 448 assert Person.new.contributor? 449 449 450 450 assert_nothing_raised { Person.connection.change_column "people", "contributor", :boolean, :default => nil } 451 451 Person.reset_column_information 452 452 assert !Person.new.contributor? … … 466 466 ensure 467 467 Person.connection.remove_column("people", "administrator") rescue nil 468 468 end 469 469 470 470 def test_change_column_default 471 471 Person.connection.change_column_default "people", "first_name", "Tester" 472 472 Person.reset_column_information … … 731 731 def test_migrator_with_missing_version_numbers 732 732 ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 500) 733 733 assert !Person.column_methods_hash.include?(:middle_name) 734 assert_equal 4, ActiveRecord::Migrator.current_version735 736 ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 2)737 assert !Reminder.table_exists?738 assert Person.column_methods_hash.include?(:last_name) 739 assert_equal 2, ActiveRecord::Migrator.current_version734 assert_equal 4, ActiveRecord::Migrator.current_version 735 736 ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/fixtures/migrations_with_missing_versions/', 2) 737 assert !Reminder.table_exists? 738 assert Person.column_methods_hash.include?(:last_name) 739 assert_equal 2, ActiveRecord::Migrator.current_version 740 740 end 741 741 742 742 def test_create_table_with_custom_sequence_name 743 743 return unless current_adapter? :OracleAdapter 744 744 … … 778 778 779 779 end 780 780 end 781 -
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 = [27 ARRAY_TABLE_NAME = 'postgresql_arrays' 28 ARRAY_COLUMNS = [ 11 29 'id SERIAL PRIMARY KEY', 12 30 'commission_by_quarter INTEGER[]', 13 31 'nicknames TEXT[]' 14 32 ] 15 33 34 MONEY_TABLE_NAME = 'postgresql_moneys' 35 MONEY_COLUMNS = [ 36 'id SERIAL PRIMARY KEY', 37 'wealth MONEY' 38 ] 39 40 NUMBER_TABLE_NAME = 'postgresql_numbers' 41 NUMBER_COLUMNS = [ 42 'id SERIAL PRIMARY KEY', 43 'single REAL', 44 'double DOUBLE PRECISION' 45 ] 46 47 TIME_TABLE_NAME = 'postgresql_times' 48 TIME_COLUMNS = [ 49 'id SERIAL PRIMARY KEY', 50 'time_interval INTERVAL' 51 ] 52 53 NETWORK_ADDRESS_TABLE_NAME = 'postgresql_network_addresses' 54 NETWORK_ADDRESS_COLUMNS = [ 55 'id SERIAL PRIMARY KEY', 56 'cidr_address CIDR', 57 'inet_address INET', 58 'mac_address MACADDR' 59 ] 60 61 BIT_STRING_TABLE_NAME = 'postgresql_bit_strings' 62 BIT_STRING_COLUMNS = [ 63 'id SERIAL PRIMARY KEY', 64 'bit_string BIT(8)', 65 'bit_string_varying BIT VARYING(8)' 66 ] 67 68 OID_TABLE_NAME = 'postgresql_oids' 69 OID_COLUMNS = [ 70 'id SERIAL PRIMARY KEY', 71 'obj_id OID' 72 ] 73 16 74 def setup 17 75 @connection = ActiveRecord::Base.connection 18 @connection.execute "CREATE TABLE #{TABLE_NAME} (#{COLUMNS.join(',')})" 19 @connection.execute "INSERT INTO #{TABLE_NAME} (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )" 20 @first = PostgresqlDatatype.find( 1 ) 76 77 @connection.execute("CREATE TABLE #{ARRAY_TABLE_NAME} (#{ARRAY_COLUMNS.join(',')})") 78 @connection.execute("INSERT INTO #{ARRAY_TABLE_NAME} (commission_by_quarter, nicknames) VALUES ( '{35000,21000,18000,17000}', '{foo,bar,baz}' )") 79 @first_array = PostgresqlArray.find(1) 80 81 @connection.execute("CREATE TABLE #{MONEY_TABLE_NAME} (#{MONEY_COLUMNS.join(',')})") 82 @connection.execute("INSERT INTO #{MONEY_TABLE_NAME} (wealth) VALUES ('$567.89')") 83 @connection.execute("INSERT INTO #{MONEY_TABLE_NAME} (wealth) VALUES ('-$567.89')") 84 @first_money = PostgresqlMoney.find(1) 85 @second_money = PostgresqlMoney.find(2) 86 87 @connection.execute("CREATE TABLE #{NUMBER_TABLE_NAME} (#{NUMBER_COLUMNS.join(',')})") 88 @connection.execute("INSERT INTO #{NUMBER_TABLE_NAME} (single, double) VALUES (123.456, 123456.789)") 89 @first_number = PostgresqlNumber.find(1) 90 91 @connection.execute("CREATE TABLE #{TIME_TABLE_NAME} (#{TIME_COLUMNS.join(',')})") 92 @connection.execute("INSERT INTO #{TIME_TABLE_NAME} (time_interval) VALUES ('1 year 2 days ago')") 93 @first_time = PostgresqlTime.find(1) 94 95 @connection.execute("CREATE TABLE #{NETWORK_ADDRESS_TABLE_NAME} (#{NETWORK_ADDRESS_COLUMNS.join(',')})") 96 @connection.execute("INSERT INTO #{NETWORK_ADDRESS_TABLE_NAME} (cidr_address, inet_address, mac_address) VALUES('192.168.0/24', '172.16.1.254/32', '01:23:45:67:89:0a')") 97 @first_network_address = PostgresqlNetworkAddress.find(1) 98 99 @connection.execute("CREATE TABLE #{BIT_STRING_TABLE_NAME} (#{BIT_STRING_COLUMNS.join(',')})") 100 @connection.execute("INSERT INTO #{BIT_STRING_TABLE_NAME} (bit_string, bit_string_varying) VALUES (B'00010101', X'15')") 101 @first_bit_string = PostgresqlBitString.find(1) 102 103 @connection.execute("CREATE TABLE #{OID_TABLE_NAME} (#{OID_COLUMNS.join(',')})") 104 @connection.execute("INSERT INTO #{OID_TABLE_NAME} (obj_id) VALUES (1234)") 105 @first_oid = PostgresqlOid.find(1) 21 106 end 22 107 23 108 def teardown 24 @connection.execute "DROP TABLE #{TABLE_NAME}" 109 @connection.execute("DROP TABLE #{ARRAY_TABLE_NAME}") 110 @connection.execute("DROP TABLE #{MONEY_TABLE_NAME}") 111 @connection.execute("DROP TABLE #{NUMBER_TABLE_NAME}") 112 @connection.execute("DROP TABLE #{TIME_TABLE_NAME}") 113 @connection.execute("DROP TABLE #{NETWORK_ADDRESS_TABLE_NAME}") 114 @connection.execute("DROP TABLE #{BIT_STRING_TABLE_NAME}") 115 @connection.execute("DROP TABLE #{OID_TABLE_NAME}") 25 116 end 26 117 27 118 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").type119 assert_equal :string, @first_array.column_for_attribute("commission_by_quarter").type 120 assert_equal :string, @first_array.column_for_attribute("nicknames").type 30 121 end 31 122 123 def test_data_type_of_money_types 124 assert_equal :decimal, @first_money.column_for_attribute("wealth").type 125 end 126 127 def test_data_type_of_number_types 128 assert_equal :float, @first_number.column_for_attribute("single").type 129 assert_equal :float, @first_number.column_for_attribute("double").type 130 end 131 132 def test_data_type_of_time_types 133 assert_equal :string, @first_time.column_for_attribute("time_interval").type 134 end 135 136 def test_data_type_of_network_address_types 137 assert_equal :string, @first_network_address.column_for_attribute("cidr_address").type 138 assert_equal :string, @first_network_address.column_for_attribute("inet_address").type 139 assert_equal :string, @first_network_address.column_for_attribute("mac_address").type 140 end 141 142 def test_data_type_of_bit_string_types 143 assert_equal :string, @first_bit_string.column_for_attribute("bit_string").type 144 assert_equal :string, @first_bit_string.column_for_attribute("bit_string_varying").type 145 end 146 147 def test_data_type_of_oid_types 148 assert_equal :integer, @first_oid.column_for_attribute("obj_id").type 149 end 150 32 151 def test_array_values 33 assert_equal '{35000,21000,18000,17000}', @first .commission_by_quarter34 assert_equal '{foo,bar,baz}', @first .nicknames152 assert_equal '{35000,21000,18000,17000}', @first_array.commission_by_quarter 153 assert_equal '{foo,bar,baz}', @first_array.nicknames 35 154 end 36 155 156 def test_money_values 157 assert_equal 567.89, @first_money.wealth 158 assert_equal -567.89, @second_money.wealth 159 end 160 161 def test_number_values 162 assert_equal 123.456, @first_number.single 163 assert_equal 123456.789, @first_number.double 164 end 165 166 def test_time_values 167 assert_equal '-1 years -2 days', @first_time.time_interval 168 end 169 170 def test_network_address_values 171 assert_equal '192.168.0.0/24', @first_network_address.cidr_address 172 assert_equal '172.16.1.254', @first_network_address.inet_address 173 assert_equal '01:23:45:67:89:0a', @first_network_address.mac_address 174 end 175 176 def test_bit_string_values 177 assert_equal '00010101', @first_bit_string.bit_string 178 assert_equal '00010101', @first_bit_string.bit_string_varying 179 end 180 181 def test_oid_values 182 assert_equal 1234, @first_oid.obj_id 183 end 184 37 185 def test_update_integer_array 38 186 new_value = '{32800,95000,29350,17000}' 39 assert @first .commission_by_quarter = new_value40 assert @first .save41 assert @first .reload42 assert_equal @first .commission_by_quarter, new_value187 assert @first_array.commission_by_quarter = new_value 188 assert @first_array.save 189 assert @first_array.reload 190 assert_equal @first_array.commission_by_quarter, new_value 43 191 end 44 192 45 193 def test_update_text_array 46 194 new_value = '{robby,robert,rob,robbie}' 47 assert @first .nicknames = new_value48 assert @first .save49 assert @first .reload50 assert_equal @first .nicknames, new_value195 assert @first_array.nicknames = new_value 196 assert @first_array.save 197 assert @first_array.reload 198 assert_equal @first_array.nicknames, new_value 51 199 end 200 201 def test_update_money 202 new_value = 123.45 203 assert @first_money.wealth = new_value 204 assert @first_money.save 205 assert @first_money.reload 206 assert_equal @first_money.wealth, new_value 207 end 208 209 def test_update_number 210 new_single = 789.012 211 new_double = 789012.345 212 assert @first_number.single = new_single 213 assert @first_number.double = new_double 214 assert @first_number.save 215 assert @first_number.reload 216 assert_equal @first_number.single, new_single 217 assert_equal @first_number.double, new_double 218 end 219 220 def test_update_time 221 assert @first_time.time_interval = '2 years 3 minutes' 222 assert @first_time.save 223 assert @first_time.reload 224 assert_equal @first_time.time_interval, '2 years 00:03:00' 225 end 226 227 def test_update_network_address 228 new_cidr_address = '10.1.2.3/32' 229 new_inet_address = '10.0.0.0/8' 230 new_mac_address = 'bc:de:f0:12:34:56' 231 assert @first_network_address.cidr_address = new_cidr_address 232 assert @first_network_address.inet_address = new_inet_address 233 assert @first_network_address.mac_address = new_mac_address 234 assert @first_network_address.save 235 assert @first_network_address.reload 236 assert_equal @first_network_address.cidr_address, new_cidr_address 237 assert_equal @first_network_address.inet_address, new_inet_address 238 assert_equal @first_network_address.mac_address, new_mac_address 239 end 240 241 def test_update_bit_string 242 new_bit_string = '11111111' 243 new_bit_string_varying = 'FF' 244 assert @first_bit_string.bit_string = new_bit_string 245 assert @first_bit_string.bit_string_varying = new_bit_string_varying 246 assert @first_bit_string.save 247 assert @first_bit_string.reload 248 assert_equal @first_bit_string.bit_string, new_bit_string 249 assert_equal @first_bit_string.bit_string, @first_bit_string.bit_string_varying 250 end 251 252 def test_update_oid 253 new_value = 567890 254 assert @first_oid.obj_id = new_value 255 assert @first_oid.save 256 assert @first_oid.reload 257 assert_equal @first_oid.obj_id, new_value 258 end 52 259 end -
activerecord/test/finder_test.rb
old new 232 232 end 233 233 234 234 def test_bind_enumerable 235 quoted_abc = %(#{ActiveRecord::Base.connection.quote('a')},#{ActiveRecord::Base.connection.quote('b')},#{ActiveRecord::Base.connection.quote('c')}) 236 235 237 assert_equal '1,2,3', bind('?', [1, 2, 3]) 236 assert_equal %('a','b','c'), bind('?', %w(a b c))238 assert_equal quoted_abc, bind('?', %w(a b c)) 237 239 238 240 assert_equal '1,2,3', bind(':a', :a => [1, 2, 3]) 239 assert_equal %('a','b','c'), bind(':a', :a => %w(a b c)) # '241 assert_equal quoted_abc, bind(':a', :a => %w(a b c)) # ' 240 242 241 243 require 'set' 242 244 assert_equal '1,2,3', bind('?', Set.new([1, 2, 3])) 243 assert_equal %('a','b','c'), bind('?', Set.new(%w(a b c)))245 assert_equal quoted_abc, bind('?', Set.new(%w(a b c))) 244 246 245 247 assert_equal '1,2,3', bind(':a', :a => Set.new([1, 2, 3])) 246 assert_equal %('a','b','c'), bind(':a', :a => Set.new(%w(a b c))) # '248 assert_equal quoted_abc, bind(':a', :a => Set.new(%w(a b c))) # ' 247 249 end 248 250 249 251 def test_bind_empty_enumerable … … 254 256 end 255 257 256 258 def test_bind_string 257 assert_equal "''", bind('?', '')259 assert_equal ActiveRecord::Base.connection.quote(''), bind('?', '') 258 260 end 259 261 260 262 def test_bind_record … … 267 269 268 270 def test_string_sanitation 269 271 assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") 270 assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table") 272 assert_not_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1") 273 assert_equal "#{ActiveRecord::Base.connection.quoted_string_prefix}'something; select table'", ActiveRecord::Base.sanitize("something; select table") 271 274 end 272 275 273 276 def test_count -
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 2 2 3 3 module ActiveRecord 4 4 class Base 5 # Establishes a connection to the database that's used by all Active Record objects 6 def self.postgresql_connection(config) # :nodoc:7 require_library_or_gem 'postgres'unless self.class.const_defined?(:PGconn)5 # Establishes a connection to the database that's used by all Active Record objects. 6 def self.postgresql_connection(config) 7 require_library_or_gem('postgres') unless self.class.const_defined?(:PGconn) 8 8 9 9 config = config.symbolize_keys 10 10 host = config[:host] … … 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 raise ArgumentError, "No database specified. Missing argument: database."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 if sql_type =~ /^money/ 40 2 41 else 42 super 43 end 44 end 30 45 31 pga 46 # Extracts the precision from PostgreSQL-specific data types. 47 def extract_precision(sql_type) 48 # Actual code is defined dynamically in PostgreSQLAdapter.connect 49 super 50 end 51 52 # Escapes binary strings for bytea input to the database. 53 def self.string_to_binary(value) 54 if PGconn.respond_to?(:escape_bytea) 55 self.class.module_eval do 56 define_method(:string_to_binary) do |value| 57 PGconn.escape_bytea(value) if value 58 end 59 end 60 else 61 self.class.module_eval do 62 define_method(:string_to_binary) do |value| 63 if value 64 result = '' 65 value.each_byte { |c| result << sprintf('\\\\%03o', c) } 66 result 67 end 68 end 69 end 70 end 71 self.class.string_to_binary(value) 72 end 73 74 # Unescapes bytea output from a database to the binary string it represents. 75 def self.binary_to_string(value) 76 # In each case, check if the value actually is escaped PostgresSQL bytea output 77 # or an unescaped Active Record attribute that was just written. 78 if PGconn.respond_to?(:unescape_bytea) 79 self.class.module_eval do 80 define_method(:binary_to_string) do |value| 81 if value =~ /\\\\\d{3}/ 82 PGconn.unescape_bytea(value) 83 else 84 value 85 end 86 end 87 end 88 else 89 self.class.module_eval do 90 define_method(:binary_to_string) do |value| 91 if value =~ /\\\\\d{3}/ 92 result = '' 93 i, max = 0, value.size 94 while i < max 95 char = value[i] 96 if char == ?\\ 97 if value[i+1] == ?\\ 98 char = ?\\ 99 i += 1 100 else 101 char = value[i+1..i+3].oct 102 i += 3 103 end 104 end 105 result << char 106 i += 1 107 end 108 result 109 else 110 value 111 end 112 end 113 end 114 end 115 self.class.binary_to_string(value) 116 end 117 118 # Maps PostgreSQL-specific data types to logical Rails types. 119 def simplified_type(field_type) 120 case field_type 121 # Numeric and monetary types 122 when /^(?:real|double precision)$/ 123 :float 124 # Monetary types 125 when /^money$/ 126 :decimal 127 # Character types 128 when /^(?:character varying|bpchar)(?:\(\d+\))?$/ 129 :string 130 # Binary data types 131 when /^bytea$/ 132 :binary 133 # Date/time types 134 when /^timestamp with(?:out)? time zone$/ 135 :datetime 136 when /^interval$/ 137 :string 138 # Geometric types 139 when /^(?:point|line|lseg|box|"?path"?|polygon|circle)$/ 140 :string 141 # Network address types 142 when /^(?:cidr|inet|macaddr)$/ 143 :string 144 # Bit strings 145 when /^bit(?: varying)?(?:\(\d+\))?$/ 146 :string 147 # XML type 148 when /^xml$/ 149 :string 150 # Arrays 151 when /^\D+\[\]$/ 152 :string 153 # Object identifier types 154 when /^oid$/ 155 :integer 156 # Pass through all types that are not specific to PostgreSQL. 157 else 158 super 159 end 160 end 161 162 # Extracts the value from a PostgreSQL column default definition. 163 def self.extract_value_from_default(default) 164 case default 165 # Numeric types 166 when /^-?\d+(\.\d*)?$/ 167 default 168 # Character types 169 when /^'(.*)'::(?:character varying|bpchar|text)$/ 170 $1 171 # Binary data types 172 when /^'(.*)'::bytea$/ 173 $1 174 # Date/time types 175 when /^'(.+)'::(?:time(?:stamp)? with(?:out)? time zone|date)$/ 176 $1 177 when /^'(.*)'::interval$/ 178 $1 179 # Boolean type 180 when /^true$/ 181 true 182 when /^false$/ 183 false 184 # Geometric types 185 when /^'(.*)'::(?:point|line|lseg|box|"?path"?|polygon|circle)$/ 186 $1 187 # Network address types 188 when /^'(.*)'::(?:cidr|inet|macaddr)$/ 189 $1 190 # Bit string types 191 when /^B'(.*)'::"?bit(?: varying)?"?$/ 192 $1 193 # XML type 194 when /^'(.*)'::xml$/ 195 $1 196 # Arrays 197 when /^'(.*)'::"?\D+"?\[\]$/ 198 $1 199 # Object identifier types 200 when /^-?\d+$/ 201 $1 202 else 203 # Anything else is blank, some user type, or some function 204 # and we can't know the value of that, so return nil. 205 nil 206 end 207 end 32 208 end 33 end34 209 35 module ConnectionAdapters 36 # The PostgreSQL adapter works both with the C-based (http://www.postgresql.jp/interfaces/ruby/) and the Ruby-base 37 # (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1145) drivers. 210 # The PostgreSQL adapter works both with the native C (http://ruby.scripting.ca/postgres/) and the pure 211 # Ruby (available both as gem and from http://rubyforge.org/frs/?group_id=234&release_id=1944) drivers. 38 212 # 39 213 # Options: 40 214 # … … 48 222 # * <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 223 # * <tt>:allow_concurrency</tt> -- If true, use async query methods so Ruby threads don't deadlock; otherwise, use blocking query methods. 50 224 class PostgreSQLAdapter < AbstractAdapter 225 # Returns 'PostgreSQL' as adapter name for identification purposes. 51 226 def adapter_name 52 227 'PostgreSQL' 53 228 end 54 229 55 def initialize(connection, logger, config = {}) 230 # Initializes and connects a PostgreSQL adapter. 231 def initialize(connection, logger, connection_parameters, config) 56 232 super(connection, logger) 57 @con fig =config233 @connection_parameters, @config = connection_parameters, config 58 234 59 # Ignore async_exec and async_query with the postgres-pr client lib. 60 @async = config[:allow_concurrency] && @connection.respond_to?(:async_exec) 61 62 configure_connection 235 connect 63 236 end 64 237 65 # Is this connection alive and ready for queries?238 # Returns if this connection is alive and ready for queries. 66 239 def active? 67 240 if @connection.respond_to?(:status) 68 241 @connection.status == PGconn::CONNECTION_OK 69 242 else 70 @connection.query 'SELECT 1' 243 # We're asking the driver, not ActiveRecord, so use @connection.query instead of #query 244 @connection.query('SELECT 1') 71 245 true 72 246 end 73 # postgres-pr raises a NoMethodError when querying if no conn is available247 # postgres-pr raises a NoMethodError when querying if no connection is available. 74 248 rescue PGError, NoMethodError 75 249 false 76 250 end 77 251 78 # Close then reopenthe connection.252 # Closes and then reopens the connection. 79 253 def reconnect! 80 # TODO: postgres-pr doesn't have PGconn#reset.81 254 if @connection.respond_to?(:reset) 82 255 @connection.reset 83 256 configure_connection 257 else 258 disconnect! 259 connect 84 260 end 85 261 end 86 262 263 # Closes the connection. 87 264 def disconnect! 88 # Both postgres and postgres-pr respond to :close89 265 @connection.close rescue nil 90 266 end 91 267 92 def native_database_types 268 def native_database_types #:nodoc: 93 269 { 94 :primary_key => "serial primary key",95 :string => { :name => "character varying", :limit => 255 },96 :text => { :name => "text"},97 :integer => { :name => "integer"},98 :float => { :name => "float"},99 :decimal => { :name => "decimal"},100 :datetime => { :name => "timestamp"},101 :timestamp => { :name => "timestamp"},102 :time => { :name => "time"},103 :date => { :name => "date"},104 :binary => { :name => "bytea"},105 :boolean => { :name => "boolean"}270 :primary_key => 'serial PRIMARY KEY', 271 :string => { :name => 'character varying', :limit => 255 }, 272 :text => { :name => 'text' }, 273 :integer => { :name => 'integer' }, 274 :float => { :name => 'float' }, 275 :decimal => { :name => 'decimal' }, 276 :datetime => { :name => 'timestamp' }, 277 :timestamp => { :name => 'timestamp' }, 278 :time => { :name => 'time' }, 279 :date => { :name => 'date' }, 280 :binary => { :name => 'bytea' }, 281 :boolean => { :name => 'boolean' } 106 282 } 107 283 end 108 284 285 # Returns true to indicate that PostgreSQL supports migrations. 109 286 def supports_migrations? 110 287 true 111 288 end 112 289 290 # Returns the configured supported identifier length supported by PostgreSQL, 291 # or report the default of 63 on PostgreSQL 7. 113 292 def table_alias_length 114 63293 @table_alias_length ||= (postgresql_version >= 80000 ? query('SHOW max_identifier_length')[0][0].to_i : 63) 115 294 end 116 295 117 296 # QUOTING ================================================== 118 297 119 def quote(value, column = nil) 298 # Quotes PostgreSQL-specific data types for SQL input. 299 def quote(value, column = nil) #:nodoc: 120 300 if value.kind_of?(String) && column && column.type == :binary 121 "'#{escape_bytea(value)}'" 301 "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'" 302 elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ 303 "xml '#{quote_string(value)}'" 304 elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ 305 # Not truly string input, so doesn't require (or allow) escape string syntax. 306 "'#{value.to_s}'" 307 elsif value.kind_of?(String) && column && column.sql_type =~ /^bit/ 308 case value 309 when /^[01]*$/ 310 "B'#{value}'" # Bit-string notation 311 when /^[0-9A-F]*$/i 312 "X'#{value}'" # Hexadecimal notation 313 end 122 314 else 123 315 super 124 316 end 125 317 end 126 318 127 def quote_column_name(name) 319 # Quotes strings for use in SQL input in the postgres driver for better performance. 320 def quote_string(s) #:nodoc: 321 if PGconn.respond_to?(:escape) 322 self.class.instance_eval do 323 define_method(:quote_string) do |s| 324 PGconn.escape(s) 325 end 326 end 327 else 328 # There are some incorrectly compiled postgres drivers out there 329 # that don't define PGconn.escape. 330 self.class.instance_eval do 331 undef_method(:quote_string) 332 end 333 end 334 quote_string(s) 335 end 336 337 # Quotes column names for use in SQL queries. 338 def quote_column_name(name) #:nodoc: 128 339 %("#{name}") 129 340 end 130 341 131 # Include microseconds if the value is a Time responding to usec. 132 def quoted_date(value) 342 # Quote date/time values for use in SQL input. Includes microseconds 343 # if the value is a Time responding to usec. 344 def quoted_date(value) #:nodoc: 133 345 if value.acts_like?(:time) && value.respond_to?(:usec) 134 346 "#{super}.#{sprintf("%06d", value.usec)}" 135 347 else … … 137 349 end 138 350 end 139 351 140 141 352 # DATABASE STATEMENTS ====================================== 142 353 143 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: 354 # Executes an INSERT query and returns the new record's ID 355 def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) 144 356 execute(sql, name) 145 table = sql.split( " ", 4)[2]357 table = sql.split(' ', 4)[2] 146 358 id_value || last_insert_id(table, sequence_name || default_sequence_name(table, pk)) 147 359 end 148 360 361 # Queries the database and returns the results in an Array or nil otherwise. 149 362 def query(sql, name = nil) #:nodoc: 150 363 log(sql, name) do 151 364 if @async … … 156 369 end 157 370 end 158 371 159 def execute(sql, name = nil) #:nodoc: 372 # Executes a SQL statement, returning a PGresult object on success 373 # or raising a PGError exception otherwise. 374 def execute(sql, name = nil) 160 375 log(sql, name) do 161 376 if @async 162 377 @connection.async_exec(sql) … … 166 381 end 167 382 end 168 383 169 def update(sql, name = nil) #:nodoc: 384 # Executes an UPDATE query and returns the number of affected tuples. 385 def update(sql, name = nil) 170 386 execute(sql, name).cmdtuples 171 387 end 172 388 173 def begin_db_transaction #:nodoc: 174 execute "BEGIN" 389 # Begins a transaction. 390 def begin_db_transaction 391 execute('BEGIN') 175 392 end 176 393 177 def commit_db_transaction #:nodoc: 178 execute "COMMIT" 394 # Commits a transaction. 395 def commit_db_transaction 396 execute('COMMIT') 179 397 end 180 398 181 def rollback_db_transaction #:nodoc: 182 execute "ROLLBACK" 399 # Aborts a transaction. 400 def rollback_db_transaction 401 execute('ROLLBACK') 183 402 end 184 403 185 404 # SCHEMA STATEMENTS ======================================== 186 405 187 # Return the list of all tables in the schema search path.188 def tables(name = nil) #:nodoc:406 # Returns the list of all tables in the schema search path or a specified schema. 407 def tables(name = nil) 189 408 schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') 190 409 query(<<-SQL, name).map { |row| row[0] } 191 410 SELECT tablename … … 194 413 SQL 195 414 end 196 415 197 def indexes(table_name, name = nil) #:nodoc: 416 # Returns the list of all indexes for a table. 417 def indexes(table_name, name = nil) 198 418 result = query(<<-SQL, name) 199 419 SELECT i.relname, d.indisunique, a.attname 200 420 FROM pg_class t, pg_class i, pg_index d, pg_attribute a … … 217 437 218 438 result.each do |row| 219 439 if current_index != row[0] 220 indexes << IndexDefinition.new(table_name, row[0], row[1] == "t", [])440 indexes << IndexDefinition.new(table_name, row[0], row[1] == 't', []) 221 441 current_index = row[0] 222 442 end 223 443 … … 227 447 indexes 228 448 end 229 449 230 def columns(table_name, name = nil) #:nodoc: 231 column_definitions(table_name).collect do |name, type, default, notnull, typmod| 232 # typmod now unused as limit, precision, scale all handled by superclass 233 Column.new(name, default_value(default), translate_field_type(type), notnull == "f") 450 # Returns the list of all column definitions for a table. 451 def columns(table_name, name = nil) 452 # Limit, precision, and scale are all handled by superclass. 453 column_definitions(table_name).collect do |name, type, default, notnull| 454 PostgreSQLColumn.new(name, default, type, notnull == 'f') 234 455 end 235 456 end 236 457 237 # Set the schema search path to a string of comma-separated schema names. 238 # Names beginning with $ are quoted (e.g. $user => '$user') 239 # See http://www.postgresql.org/docs/8.0/interactive/ddl-schemas.html 240 def schema_search_path=(schema_csv) #:nodoc: 458 # Sets the schema search path to a string of comma-separated schema names. 459 # Names beginning with $ have to be quoted (e.g. $user => '$user'). 460 # See: http://www.postgresql.org/docs/current/static/ddl-schemas.html 461 # 462 # This should be not be called manually but set in database.yml. 463 def schema_search_path=(schema_csv) 241 464 if schema_csv 242 execute "SET search_path TO #{schema_csv}"243 @schema_search_path = nil465 execute("SET search_path TO #{schema_csv}") 466 @schema_search_path = schema_csv 244 467 end 245 468 end 246 469 247 def schema_search_path #:nodoc: 470 # Returns the active schema search path. 471 def schema_search_path 248 472 @schema_search_path ||= query('SHOW search_path')[0][0] 249 473 end 250 474 251 def default_sequence_name(table_name, pk = nil) 475 # Returns the sequence name for a table's primary key or some other specified key. 476 def default_sequence_name(table_name, pk = nil) #:nodoc: 252 477 default_pk, default_seq = pk_and_sequence_for(table_name) 253 478 default_seq || "#{table_name}_#{pk || default_pk || 'id'}_seq" 254 479 end 255 480 256 # Resets sequence to the max value of the table's pk if present.257 def reset_pk_sequence!(table, pk = nil, sequence = nil) 258 unless pk andsequence481 # Resets the sequence of a table's primary key to the maximum value. 482 def reset_pk_sequence!(table, pk = nil, sequence = nil) #:nodoc: 483 unless pk && sequence 259 484 default_pk, default_sequence = pk_and_sequence_for(table) 260 485 pk ||= default_pk 261 486 sequence ||= default_sequence 262 487 end 263 488 if pk 264 489 if sequence 265 select_value <<-end_sql, 'Reset sequence'490 select_value(<<-end_sql, 'Reset sequence') 266 491 SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false) 267 492 end_sql 268 493 else 269 @logger.warn "#{table} has primary key #{pk} with no default sequence"if @logger494 @logger.warn("#{table} has primary key #{pk} with no default sequence") if @logger 270 495 end 271 496 end 272 497 end 273 498