diff options
Diffstat (limited to 'activerecord')
47 files changed, 820 insertions, 261 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 8630c82906..cf85ec637a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* Fix bug that added `table_name_prefix` and `table_name_suffix` to + extension names in PostgreSQL when migrating. + + *Joao Carlos* + +* The `:index` option in migrations, which previously was only available for + `references`, now works with any column types. + + *Marc Schütz* + * Add support for counter name to be passed as parameter on `CounterCache::ClassMethods#reset_counters`. *jnormore* diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 01ca4c82f2..7769966a22 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -38,33 +38,36 @@ namespace :test do end end +desc 'Build MySQL and PostgreSQL test databases' namespace :db do - desc 'Build MySQL and PostgreSQL test databases' - task create: ['mysql:build_databases', 'postgresql:build_databases'] - desc 'Drop MySQL and PostgreSQL test databases' - task drop: ['mysql:drop_databases', 'postgresql:drop_databases'] + task :create => ['db:mysql:build', 'db:postgresql:build'] + task :drop => ['db:mysql:drop', 'db:postgresql:drop'] end %w( mysql mysql2 postgresql sqlite3 sqlite3_mem db2 oracle jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| - Rake::TestTask.new("test_#{adapter}") { |t| - adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] - t.libs << 'test' - t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { - |x| x =~ /\/adapters\// - } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort - - t.warning = true - t.verbose = true - } - - task "isolated_test_#{adapter}" do - adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] - puts [adapter, adapter_short].inspect - (Dir["test/cases/**/*_test.rb"].reject { - |x| x =~ /\/adapters\// - } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| - sh(Gem.ruby, '-w' ,"-Itest", file) - end or raise "Failures" + namespace :test do + Rake::TestTask.new(adapter => "#{adapter}:env") { |t| + adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] + t.libs << 'test' + t.test_files = (Dir.glob( "test/cases/**/*_test.rb" ).reject { + |x| x =~ /\/adapters\// + } + Dir.glob("test/cases/adapters/#{adapter_short}/**/*_test.rb")).sort + + t.warning = true + t.verbose = true + } + + namespace :isolated do + task adapter => "#{adapter}:env" do + adapter_short = adapter == 'db2' ? adapter : adapter[/^[a-z0-9]+/] + puts [adapter, adapter_short].inspect + (Dir["test/cases/**/*_test.rb"].reject { + |x| x =~ /\/adapters\// + } + Dir["test/cases/adapters/#{adapter_short}/**/*_test.rb"]).all? do |file| + sh(Gem.ruby, '-w' ,"-Itest", file) + end or raise "Failures" + end + end end namespace adapter do @@ -76,8 +79,8 @@ end end # Make sure the adapter test evaluates the env setting task - task "test_#{adapter}" => "#{adapter}:env" - task "isolated_test_#{adapter}" => "#{adapter}:env" + task "test_#{adapter}" => ["#{adapter}:env", "test:#{adapter}"] + task "isolated_test_#{adapter}" => ["#{adapter}:env", "test:isolated:#{adapter}"] end rule '.sqlite3' do |t| @@ -89,63 +92,58 @@ task :test_sqlite3 => [ 'test/fixtures/fixture_database_2.sqlite3' ] -namespace :mysql do - desc 'Build the MySQL test databases' - task :build_databases do - config = ARTest.config['connections']['mysql'] - %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") - end - - desc 'Drop the MySQL test databases' - task :drop_databases do - config = ARTest.config['connections']['mysql'] - %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) - %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) - end +namespace :db do + namespace :mysql do + desc 'Build the MySQL test databases' + task :build do + config = ARTest.config['connections']['mysql'] + %x( mysql --user=#{config['arunit']['username']} -e "create DATABASE #{config['arunit']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + %x( mysql --user=#{config['arunit2']['username']} -e "create DATABASE #{config['arunit2']['database']} DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci ") + end - desc 'Rebuild the MySQL test databases' - task :rebuild_databases => [:drop_databases, :build_databases] -end + desc 'Drop the MySQL test databases' + task :drop do + config = ARTest.config['connections']['mysql'] + %x( mysqladmin --user=#{config['arunit']['username']} -f drop #{config['arunit']['database']} ) + %x( mysqladmin --user=#{config['arunit2']['username']} -f drop #{config['arunit2']['database']} ) + end -task :build_mysql_databases => 'mysql:build_databases' -task :drop_mysql_databases => 'mysql:drop_databases' -task :rebuild_mysql_databases => 'mysql:rebuild_databases' + desc 'Rebuild the MySQL test databases' + task :rebuild => [:drop, :build] + end + namespace :postgresql do + desc 'Build the PostgreSQL test databases' + task :build do + config = ARTest.config['connections']['postgresql'] + %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) + %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) -namespace :postgresql do - desc 'Build the PostgreSQL test databases' - task :build_databases do - config = ARTest.config['connections']['postgresql'] - %x( createdb -E UTF8 -T template0 #{config['arunit']['database']} ) - %x( createdb -E UTF8 -T template0 #{config['arunit2']['database']} ) + # prepare hstore + if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" + puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" + end + end - # notify about preparing hstore - if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" - puts "Please prepare hstore data type. See http://www.postgresql.org/docs/9.0/static/hstore.html" + desc 'Drop the PostgreSQL test databases' + task :drop do + config = ARTest.config['connections']['postgresql'] + %x( dropdb #{config['arunit']['database']} ) + %x( dropdb #{config['arunit2']['database']} ) end - end - desc 'Drop the PostgreSQL test databases' - task :drop_databases do - config = ARTest.config['connections']['postgresql'] - %x( dropdb #{config['arunit']['database']} ) - %x( dropdb #{config['arunit2']['database']} ) + desc 'Rebuild the PostgreSQL test databases' + task :rebuild => [:drop, :build] end - - desc 'Rebuild the PostgreSQL test databases' - task :rebuild_databases => [:drop_databases, :build_databases] end -task :build_postgresql_databases => 'postgresql:build_databases' -task :drop_postgresql_databases => 'postgresql:drop_databases' -task :rebuild_postgresql_databases => 'postgresql:rebuild_databases' - +task :build_mysql_databases => 'db:mysql:build' +task :drop_mysql_databases => 'db:mysql:drop' +task :rebuild_mysql_databases => 'db:mysql:rebuild' -spec = eval(File.read('activerecord.gemspec')) -Gem::PackageTask.new(spec) do |p| - p.gem_spec = spec -end +task :build_postgresql_databases => 'db:postgresql:build' +task :drop_postgresql_databases => 'db:postgresql:drop' +task :rebuild_postgresql_databases => 'db:postgresql:rebuild' task :lines do lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 @@ -171,6 +169,11 @@ task :lines do puts "Total: Lines #{total_lines}, LOC #{total_codelines}" end +spec = eval(File.read('activerecord.gemspec')) + +Gem::PackageTask.new(spec) do |p| + p.gem_spec = spec +end # Publishing ------------------------------------------------------ diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 0d5313956b..45c275a017 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -230,8 +230,8 @@ module ActiveRecord private def reader_method(name, class_name, mapping, allow_nil, constructor) define_method(name) do - if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? }) - attrs = mapping.collect {|pair| read_attribute(pair.first)} + if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|key, _| !read_attribute(key).nil? }) + attrs = mapping.collect {|key, _| read_attribute(key)} object = constructor.respond_to?(:call) ? constructor.call(*attrs) : class_name.constantize.send(constructor, *attrs) @@ -249,10 +249,10 @@ module ActiveRecord end if part.nil? && allow_nil - mapping.each { |pair| self[pair.first] = nil } + mapping.each { |key, _| self[key] = nil } @aggregation_cache[name] = nil else - mapping.each { |pair| self[pair.first] = part.send(pair.last) } + mapping.each { |key, value| self[key] = part.send(value) } @aggregation_cache[name] = part.freeze end end diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index caf4e612f9..c5f7bcae7d 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -244,6 +244,7 @@ module ActiveRecord # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) + return if records.empty? _options = records.extract_options! dependent = _options[:dependent] || options[:dependent] @@ -257,6 +258,7 @@ module ActiveRecord # Note that this method removes records from the database ignoring the # +:dependent+ option. def destroy(*records) + return if records.empty? records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) } delete_or_destroy(records, :destroy) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 2eaaffd08e..f54fcc4040 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -99,6 +99,8 @@ module ActiveRecord # Specifies the precision for a <tt>:decimal</tt> column. # * <tt>:scale</tt> - # Specifies the scale for a <tt>:decimal</tt> column. + # * <tt>:index</tt> - + # Create an index for the column. Can be either <tt>true</tt> or an options hash. # # For clarity's sake: the precision is the number of significant digits, # while the scale is the number of digits that can be stored following @@ -163,18 +165,21 @@ module ActiveRecord # What can be written like this with the regular calls to column: # # create_table :products do |t| - # t.column :shop_id, :integer - # t.column :creator_id, :integer - # t.column :name, :string, default: "Untitled" - # t.column :value, :string, default: "Untitled" - # t.column :created_at, :datetime - # t.column :updated_at, :datetime + # t.column :shop_id, :integer + # t.column :creator_id, :integer + # t.column :item_number, :string + # t.column :name, :string, default: "Untitled" + # t.column :value, :string, default: "Untitled" + # t.column :created_at, :datetime + # t.column :updated_at, :datetime # end + # add_index :products, :item_number # # can also be written as follows using the short-hand: # # create_table :products do |t| # t.integer :shop_id, :creator_id + # t.string :item_number, index: true # t.string :name, :value, default: "Untitled" # t.timestamps # end @@ -210,6 +215,8 @@ module ActiveRecord raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table." end + index_options = options.delete(:index) + index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options @columns_hash[name] = new_column_definition(name, type, options) self end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index d9c939689f..8a7a869eec 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -361,10 +361,46 @@ module ActiveRecord pool.checkin self end + def type_map # :nodoc: + @type_map ||= Type::TypeMap.new.tap do |mapping| + initialize_type_map(mapping) + end + end + protected def lookup_cast_type(sql_type) # :nodoc: - Type::Value.new + type_map.lookup(sql_type) + end + + def initialize_type_map(m) # :nodoc: + m.register_type %r(boolean)i, Type::Boolean.new + m.register_type %r(char)i, Type::String.new + m.register_type %r(binary)i, Type::Binary.new + m.alias_type %r(blob)i, 'binary' + m.register_type %r(text)i, Type::Text.new + m.alias_type %r(clob)i, 'text' + m.register_type %r(date)i, Type::Date.new + m.register_type %r(time)i, Type::Time.new + m.register_type %r(timestamp)i, Type::Timestamp.new + m.register_type %r(datetime)i, Type::DateTime.new + m.alias_type %r(numeric)i, 'decimal' + m.alias_type %r(number)i, 'decimal' + m.register_type %r(float)i, Type::Float.new + m.alias_type %r(double)i, 'float' + m.register_type %r(int)i, Type::Integer.new + m.register_type(%r(decimal)i) do |sql_type| + if Type.extract_scale(sql_type) == 0 + Type::Integer.new + else + Type::Decimal.new + end + end + end + + def reload_type_map # :nodoc: + type_map.clear + initialize_type_map(type_map) end def translate_exception_class(e, sql) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 7074f69583..5eb2e86d48 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -97,18 +97,6 @@ module ActiveRecord private - def simplified_type(field_type) - return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)") - - case field_type - when /enum/i, /set/i then :string - when /year/i then :integer - when /bit/i then :binary - else - super - end - end - def extract_limit(sql_type) case sql_type when /^enum\((.+)\)/i @@ -318,6 +306,11 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== + def clear_cache! + super + reload_type_map + end + # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) log(sql, name) { @connection.query(sql) } @@ -645,6 +638,15 @@ module ActiveRecord protected + def initialize_type_map(m) + super + m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans + m.alias_type %r(enum)i, 'varchar' + m.alias_type %r(set)i, 'varchar' + m.alias_type %r(year)i, 'integer' + m.alias_type %r(bit)i, 'binary' + end + # MySQL is too stupid to create a temporary table for use subquery, so we have # to give it some prompting in the form of a subsubquery. Ugh! def subquery_for(key, select) diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 3bab325e42..0087c20b88 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -13,11 +13,13 @@ module ActiveRecord ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/ end - attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale, :default_function + attr_reader :name, :default, :cast_type, :limit, :null, :sql_type, :precision, :scale, :default_function attr_accessor :primary, :coder alias :encoded? :coder + delegate :type, to: :cast_type + # Instantiates a new column in the table. # # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>. @@ -35,7 +37,6 @@ module ActiveRecord @limit = extract_limit(sql_type) @precision = extract_precision(sql_type) @scale = extract_scale(sql_type) - @type = simplified_type(sql_type) @default = extract_default(default) @default_function = nil @primary = nil @@ -256,6 +257,8 @@ module ActiveRecord end private + delegate :extract_scale, to: Type + def extract_limit(sql_type) $1.to_i if sql_type =~ /\((.*)\)/ end @@ -263,40 +266,6 @@ module ActiveRecord def extract_precision(sql_type) $2.to_i if sql_type =~ /^(numeric|decimal|number)\((\d+)(,\d+)?\)/i end - - def extract_scale(sql_type) - case sql_type - when /^(numeric|decimal|number)\((\d+)\)/i then 0 - when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i - end - end - - def simplified_type(field_type) - case field_type - when /int/i - :integer - when /float|double/i - :float - when /decimal|numeric|number/i - extract_scale(field_type) == 0 ? :integer : :decimal - when /datetime/i - :datetime - when /timestamp/i - :timestamp - when /time/i - :time - when /date/i - :date - when /clob/i, /text/i - :text - when /blob/i, /binary/i - :binary - when /char/i - :string - when /boolean/i - :boolean - end - end end end # :startdoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 69e2b0ab2b..bf09bfe217 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -223,6 +223,7 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! + super @statements.clear end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb index 743bf68fe6..1b74c039ce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/array_parser.rb @@ -1,7 +1,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL - module ArrayParser + module ArrayParser # :nodoc: DOUBLE_QUOTE = '"' BACKSLASH = "\\" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index b612602216..a14381acb6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -1,7 +1,7 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL - module Cast + module Cast # :nodoc: def point_to_string(point) # :nodoc: "(#{point[0]},#{point[1]})" end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 77fdebbbc9..1dd8acc257 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -165,11 +165,6 @@ module ActiveRecord super end end - - # Maps PostgreSQL-specific data types to logical Rails types. - def simplified_type(field_type) - @oid_type.simplified_type(field_type) || super - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index cf6a375704..90bf6c6d1a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,11 +1,8 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL - module OID - class Type - def type; end - def simplified_type(sql_type); type end - + module OID # :nodoc: + class Type < Type::Value def infinity(options = {}) ::Float::INFINITY * (options[:negative] ? -1 : 1) end @@ -136,11 +133,11 @@ module ActiveRecord end class Range < Type - attr_reader :subtype - def simplified_type(sql_type); sql_type.to_sym end + attr_reader :subtype, :type - def initialize(subtype) + def initialize(subtype, type) @subtype = subtype + @type = type end def extract_bounds(value) @@ -204,11 +201,8 @@ This is not reliable and will be removed in the future. end end - class Timestamp < Type - def type; :timestamp; end - def simplified_type(sql_type) - :datetime - end + class DateTime < Type + def type; :datetime; end def type_cast(value) return if value.nil? @@ -415,7 +409,7 @@ This is not reliable and will be removed in the future. def register_range_type(row) if subtype = @store[row['rngsubtype'].to_i] - register row['oid'], OID::Range.new(subtype) + register row['oid'], OID::Range.new(subtype, row['typname'].to_sym) end end @@ -483,7 +477,7 @@ This is not reliable and will be removed in the future. register_type 'bool', OID::Boolean.new register_type 'bit', OID::Bit.new alias_type 'varbit', 'bit' - register_type 'timestamp', OID::Timestamp.new + register_type 'timestamp', OID::DateTime.new alias_type 'timestamptz', 'timestamp' register_type 'date', OID::Date.new register_type 'time', OID::Time.new diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 0883b02a35..ad12298013 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -150,13 +150,11 @@ module ActiveRecord # - "schema.name".table_name # - "schema.name"."table.name" def quote_table_name(name) - schema, name_part = extract_pg_identifier_from_name(name.to_s) - - unless name_part - quote_column_name(schema) + schema, table = Utils.extract_schema_and_table(name.to_s) + if schema + "#{quote_column_name(schema)}.#{quote_column_name(table)}" else - table_name, name_part = extract_pg_identifier_from_name(name_part) - "#{quote_column_name(schema)}.#{quote_column_name(table_name)}" + quote_column_name(table) end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index 98dcf441ff..52b307c432 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -1,12 +1,12 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL - module ReferentialIntegrity - def supports_disable_referential_integrity? #:nodoc: + module ReferentialIntegrity # :nodoc: + def supports_disable_referential_integrity? # :nodoc: true end - def disable_referential_integrity #:nodoc: + def disable_referential_integrity # :nodoc: if supports_disable_referential_integrity? begin execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index dd983562fb..539ba38c4a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -97,7 +97,7 @@ module ActiveRecord # If the schema is not specified as part of +name+ then it will only find tables within # the current schema search path (regardless of permissions to access tables in other schemas) def table_exists?(name) - schema, table = extract_schema_and_table(name.to_s) + schema, table = Utils.extract_schema_and_table(name.to_s) return false unless table exec_query(<<-SQL, 'SCHEMA').rows.first[0].to_i > 0 @@ -488,23 +488,6 @@ module ActiveRecord [super, *order_columns].join(', ') end - - private - - # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. - # +schema_name+ is nil if not specified in +name+. - # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) - # +name+ supports the range of schema/table references understood by PostgreSQL, for example: - # - # * <tt>table_name</tt> - # * <tt>"table.name"</tt> - # * <tt>schema_name.table_name</tt> - # * <tt>schema_name."table.name"</tt> - # * <tt>"schema.name"."table name"</tt> - def extract_schema_and_table(name) - table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse - [schema, table] - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb new file mode 100644 index 0000000000..60ffd3a114 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb @@ -0,0 +1,25 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module Utils # :nodoc: + extend self + + # Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+. + # +schema_name+ is nil if not specified in +name+. + # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) + # +name+ supports the range of schema/table references understood by PostgreSQL, for example: + # + # * <tt>table_name</tt> + # * <tt>"table.name"</tt> + # * <tt>schema_name.table_name</tt> + # * <tt>schema_name."table.name"</tt> + # * <tt>"schema_name".table_name</tt> + # * <tt>"schema.name"."table name"</tt> + def extract_schema_and_table(name) + table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse + [schema, table] + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 23b91be0f3..59e157744f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,6 +1,7 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/statement_pool' +require 'active_record/connection_adapters/postgresql/utils' require 'active_record/connection_adapters/postgresql/column' require 'active_record/connection_adapters/postgresql/oid' require 'active_record/connection_adapters/postgresql/quoting' @@ -538,10 +539,6 @@ module ActiveRecord private - def type_map - @type_map - end - def get_oid_type(oid, fmod, column_name) if !type_map.key?(oid) initialize_type_map(type_map, [oid]) @@ -553,11 +550,6 @@ module ActiveRecord } end - def reload_type_map - type_map.clear - initialize_type_map(type_map) - end - def initialize_type_map(type_map, oids = nil) if supports_ranges? query = <<-SQL @@ -740,7 +732,7 @@ module ActiveRecord # Query implementation notes: # - format_type includes the column size constraint, e.g. varchar(50) # - ::regclass is a function that gives the id for a table name - def column_definitions(table_name) #:nodoc: + def column_definitions(table_name) # :nodoc: exec_query(<<-end_sql, 'SCHEMA').rows SELECT a.attname, format_type(a.atttypid, a.atttypmod), pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod @@ -752,7 +744,7 @@ module ActiveRecord end_sql end - def extract_pg_identifier_from_name(name) + def extract_pg_identifier_from_name(name) # :nodoc: match_data = name.start_with?('"') ? name.match(/\"([^\"]+)\"/) : name.match(/([^\.]+)/) if match_data @@ -762,12 +754,12 @@ module ActiveRecord end end - def extract_table_ref_from_insert_sql(sql) + def extract_table_ref_from_insert_sql(sql) # :nodoc: sql[/into\s+([^\(]*).*values\s*\(/im] $1.strip if $1 end - def create_table_definition(name, temporary, options, as = nil) + def create_table_definition(name, temporary, options, as = nil) # :nodoc: TableDefinition.new native_database_types, name, temporary, options, as end end diff --git a/activerecord/lib/active_record/connection_adapters/type.rb b/activerecord/lib/active_record/connection_adapters/type.rb index 1b27377cde..34b1e9e39e 100644 --- a/activerecord/lib/active_record/connection_adapters/type.rb +++ b/activerecord/lib/active_record/connection_adapters/type.rb @@ -1,8 +1,28 @@ require 'active_record/connection_adapters/type/value' +require 'active_record/connection_adapters/type/binary' +require 'active_record/connection_adapters/type/boolean' +require 'active_record/connection_adapters/type/date' +require 'active_record/connection_adapters/type/date_time' +require 'active_record/connection_adapters/type/decimal' +require 'active_record/connection_adapters/type/float' +require 'active_record/connection_adapters/type/integer' +require 'active_record/connection_adapters/type/string' +require 'active_record/connection_adapters/type/text' +require 'active_record/connection_adapters/type/time' +require 'active_record/connection_adapters/type/timestamp' +require 'active_record/connection_adapters/type/type_map' module ActiveRecord module ConnectionAdapters module Type # :nodoc: + class << self + def extract_scale(sql_type) + case sql_type + when /^(numeric|decimal|number)\((\d+)\)/i then 0 + when /^(numeric|decimal|number)\((\d+)(,(\d+))\)/i then $4.to_i + end + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/type/binary.rb b/activerecord/lib/active_record/connection_adapters/type/binary.rb new file mode 100644 index 0000000000..168d824d3d --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/binary.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Binary < Value # :nodoc: + def type + :binary + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/boolean.rb b/activerecord/lib/active_record/connection_adapters/type/boolean.rb new file mode 100644 index 0000000000..938d227632 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/boolean.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Boolean < Value # :nodoc: + def type + :boolean + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/date.rb b/activerecord/lib/active_record/connection_adapters/type/date.rb new file mode 100644 index 0000000000..1632f3c8f4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/date.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Date < Value # :nodoc: + def type + :date + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/date_time.rb b/activerecord/lib/active_record/connection_adapters/type/date_time.rb new file mode 100644 index 0000000000..1d7d3cfbbf --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/date_time.rb @@ -0,0 +1,13 @@ +require 'active_record/connection_adapters/type/timestamp' + +module ActiveRecord + module ConnectionAdapters + module Type + class DateTime < Timestamp # :nodoc: + def type + :datetime + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/decimal.rb b/activerecord/lib/active_record/connection_adapters/type/decimal.rb new file mode 100644 index 0000000000..5b39ea9e2f --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/decimal.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Decimal < Value # :nodoc: + def type + :decimal + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/float.rb b/activerecord/lib/active_record/connection_adapters/type/float.rb new file mode 100644 index 0000000000..089169e7c9 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/float.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Float < Value # :nodoc: + def type + :float + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/integer.rb b/activerecord/lib/active_record/connection_adapters/type/integer.rb new file mode 100644 index 0000000000..5510a11bd4 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/integer.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Integer < Value # :nodoc: + def type + :integer + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/string.rb b/activerecord/lib/active_record/connection_adapters/type/string.rb new file mode 100644 index 0000000000..0feb4299f5 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/string.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class String < Value # :nodoc: + def type + :string + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/text.rb b/activerecord/lib/active_record/connection_adapters/type/text.rb new file mode 100644 index 0000000000..ee5842a3fc --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/text.rb @@ -0,0 +1,13 @@ +require 'active_record/connection_adapters/type/string' + +module ActiveRecord + module ConnectionAdapters + module Type + class Text < String # :nodoc: + def type + :text + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/time.rb b/activerecord/lib/active_record/connection_adapters/type/time.rb new file mode 100644 index 0000000000..a3a687a8ad --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/time.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Time < Value # :nodoc: + def type + :time + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/timestamp.rb b/activerecord/lib/active_record/connection_adapters/type/timestamp.rb new file mode 100644 index 0000000000..92bf0a1954 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/timestamp.rb @@ -0,0 +1,11 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class Timestamp < Value # :nodoc: + def type + :timestamp + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/type_map.rb b/activerecord/lib/active_record/connection_adapters/type/type_map.rb new file mode 100644 index 0000000000..d89171a820 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/type/type_map.rb @@ -0,0 +1,50 @@ +module ActiveRecord + module ConnectionAdapters + module Type + class TypeMap # :nodoc: + def initialize + @mapping = {} + end + + def lookup(lookup_key) + matching_pair = @mapping.reverse_each.detect do |key, _| + key === lookup_key + end + + if matching_pair + matching_pair.last.call(lookup_key) + else + default_value + end + end + + def register_type(key, value = nil, &block) + raise ::ArgumentError unless value || block + + if block + @mapping[key] = block + else + @mapping[key] = proc { value } + end + end + + def alias_type(key, target_key) + register_type(key) do |sql_type| + metadata = sql_type[/\(.*\)/, 0] + lookup("#{target_key}#{metadata}") + end + end + + def clear + @mapping.clear + end + + private + + def default_value + @default_value ||= Value.new + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/type/value.rb b/activerecord/lib/active_record/connection_adapters/type/value.rb index 36f680050f..f7d7b9351b 100644 --- a/activerecord/lib/active_record/connection_adapters/type/value.rb +++ b/activerecord/lib/active_record/connection_adapters/type/value.rb @@ -2,6 +2,7 @@ module ActiveRecord module ConnectionAdapters module Type class Value # :nodoc: + def type; end end end end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index fc579e5c0f..8fe32bcb6c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -640,7 +640,7 @@ module ActiveRecord say_with_time "#{method}(#{arg_list})" do unless @connection.respond_to? :revert - unless arguments.empty? || method == :execute + unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method) arguments[0] = proper_table_name(arguments.first, table_name_options) arguments[1] = proper_table_name(arguments.second, table_name_options) if method == :rename_table end diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index f650e3fde8..d8a954efa8 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -9,13 +9,13 @@ module ActiveRecord end def test_type_cast_true - c = Column.new(nil, 1, Type::Value.new, 'boolean') + c = Column.new(nil, 1, Type::Boolean.new) assert_equal 1, @conn.type_cast(true, nil) assert_equal 1, @conn.type_cast(true, c) end def test_type_cast_false - c = Column.new(nil, 1, Type::Value.new, 'boolean') + c = Column.new(nil, 1, Type::Boolean.new) assert_equal 0, @conn.type_cast(false, nil) assert_equal 0, @conn.type_cast(false, c) end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 68b9e6daf7..1e7071c136 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -85,7 +85,6 @@ class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase class FullAddressType def type; :full_address end - def simplified_type(sql_type); type end def type_cast(value) if value =~ /\("?([^",]*)"?,"?([^",]*)"?\)/ diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb new file mode 100644 index 0000000000..91058f8681 --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb @@ -0,0 +1,65 @@ +require "cases/helper" +require "active_record/base" +require "active_record/connection_adapters/postgresql_adapter" + +class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + class EnableHstore < ActiveRecord::Migration + def change + enable_extension "hstore" + end + end + + class DisableHstore < ActiveRecord::Migration + def change + disable_extension "hstore" + end + end + + def setup + super + + @connection = ActiveRecord::Base.connection + + unless @connection.supports_extensions? + return skip("no extension support") + end + + @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name + @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix + @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix + + ActiveRecord::Base.table_name_prefix = "p_" + ActiveRecord::Base.table_name_suffix = "_s" + ActiveRecord::SchemaMigration.delete_all rescue nil + ActiveRecord::SchemaMigration.table_name = "p_schema_migrations_s" + ActiveRecord::Migration.verbose = false + end + + def teardown + ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix + ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix + ActiveRecord::SchemaMigration.delete_all rescue nil + ActiveRecord::Migration.verbose = true + ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name + + super + end + + def test_enable_extension_migration_ignores_prefix_and_suffix + @connection.disable_extension("hstore") + + migrations = [EnableHstore.new(nil, 1)] + ActiveRecord::Migrator.new(:up, migrations).migrate + assert @connection.extension_enabled?("hstore"), "extension hstore should be enabled" + end + + def test_disable_extension_migration_ignores_prefix_and_suffix + @connection.enable_extension("hstore") + + migrations = [DisableHstore.new(nil, 1)] + ActiveRecord::Migrator.new(:up, migrations).migrate + assert_not @connection.extension_enabled?("hstore"), "extension hstore should not be enabled" + end +end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 9d4d79c0c6..11ec7599a3 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -352,21 +352,6 @@ class SchemaTest < ActiveRecord::TestCase end end - def test_extract_schema_and_table - { - %(table_name) => [nil,'table_name'], - %("table.name") => [nil,'table.name'], - %(schema.table_name) => %w{schema table_name}, - %("schema".table_name) => %w{schema table_name}, - %(schema."table_name") => %w{schema table_name}, - %("schema"."table_name") => %w{schema table_name}, - %("even spaces".table) => ['even spaces','table'], - %(schema."table.name") => ['schema', 'table.name'] - }.each do |given, expect| - assert_equal expect, @connection.send(:extract_schema_and_table, given) - end - end - private def columns(table_name) @connection.send(:column_definitions, table_name).map do |name, type, default| diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb new file mode 100644 index 0000000000..e6d7868e9a --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -0,0 +1,20 @@ +require 'cases/helper' + +class PostgreSQLUtilsTest < ActiveSupport::TestCase + include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils + + def test_extract_schema_and_table + { + %(table_name) => [nil,'table_name'], + %("table.name") => [nil,'table.name'], + %(schema.table_name) => %w{schema table_name}, + %("schema".table_name) => %w{schema table_name}, + %(schema."table_name") => %w{schema table_name}, + %("schema"."table_name") => %w{schema table_name}, + %("even spaces".table) => ['even spaces','table'], + %(schema."table.name") => ['schema', 'table.name'] + }.each do |given, expect| + assert_equal expect, extract_schema_and_table(given) + end + end +end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 63170e710e..0c4f06d6a9 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -47,13 +47,13 @@ module ActiveRecord end def test_type_cast_true - c = Column.new(nil, 1, Type::Value.new, 'int') + c = Column.new(nil, 1, Type::Integer.new) assert_equal 't', @conn.type_cast(true, nil) assert_equal 1, @conn.type_cast(true, c) end def test_type_cast_false - c = Column.new(nil, 1, Type::Value.new, 'int') + c = Column.new(nil, 1, Type::Integer.new) assert_equal 'f', @conn.type_cast(false, nil) assert_equal 0, @conn.type_cast(false, c) end @@ -61,16 +61,16 @@ module ActiveRecord def test_type_cast_string assert_equal '10', @conn.type_cast('10', nil) - c = Column.new(nil, 1, Type::Value.new, 'int') + c = Column.new(nil, 1, Type::Integer.new) assert_equal 10, @conn.type_cast('10', c) - c = Column.new(nil, 1, Type::Value.new, 'float') + c = Column.new(nil, 1, Type::Float.new) assert_equal 10.1, @conn.type_cast('10.1', c) - c = Column.new(nil, 1, Type::Value.new, 'binary') + c = Column.new(nil, 1, Type::Binary.new) assert_equal '10.1', @conn.type_cast('10.1', c) - c = Column.new(nil, 1, Type::Value.new, 'date') + c = Column.new(nil, 1, Type::Date.new) assert_equal '10.1', @conn.type_cast('10.1', c) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index c88a843282..7c7c1fbfbd 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -774,8 +774,14 @@ class BasicsTest < ActiveRecord::TestCase assert_equal("c", duped_topic.title) end + DeveloperSalary = Struct.new(:amount) def test_dup_with_aggregate_of_same_name_as_attribute - dev = DeveloperWithAggregate.find(1) + developer_with_aggregate = Class.new(ActiveRecord::Base) do + self.table_name = 'developers' + composed_of :salary, :class_name => 'BasicsTest::DeveloperSalary', :mapping => [%w(salary amount)] + end + + dev = developer_with_aggregate.find(1) assert_kind_of DeveloperSalary, dev.salary dup = nil diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index 33aba6421e..23e6254577 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -12,13 +12,13 @@ module ActiveRecord end def test_can_set_coder - column = Column.new("title", nil, Type::Value.new, "varchar(20)") + column = Column.new("title", nil, Type::String.new, "varchar(20)") column.coder = YAML assert_equal YAML, column.coder end def test_encoded? - column = Column.new("title", nil, Type::Value.new, "varchar(20)") + column = Column.new("title", nil, Type::String.new, "varchar(20)") assert !column.encoded? column.coder = YAML @@ -26,7 +26,7 @@ module ActiveRecord end def test_type_case_coded_column - column = Column.new("title", nil, Type::Value.new, "varchar(20)") + column = Column.new("title", nil, Type::String.new, "varchar(20)") column.coder = YAML assert_equal "hello", column.type_cast("--- hello") end @@ -34,7 +34,7 @@ module ActiveRecord # Avoid column definitions in create table statements like: # `title` varchar(255) DEFAULT NULL def test_should_not_include_default_clause_when_default_is_null - column = Column.new("title", nil, Type::Value.new, "varchar(20)") + column = Column.new("title", nil, Type::String.new, "varchar(20)") column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) @@ -42,7 +42,7 @@ module ActiveRecord end def test_should_include_default_clause_when_default_is_present - column = Column.new("title", "Hello", Type::Value.new, "varchar(20)") + column = Column.new("title", "Hello", Type::String.new, "varchar(20)") column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) @@ -50,7 +50,7 @@ module ActiveRecord end def test_should_specify_not_null_if_null_option_is_false - column = Column.new("title", "Hello", Type::Value.new, "varchar(20)", false) + column = Column.new("title", "Hello", Type::String.new, "varchar(20)", false) column_def = ColumnDefinition.new( column.name, "string", column.limit, column.precision, column.scale, column.default, column.null) @@ -59,68 +59,68 @@ module ActiveRecord if current_adapter?(:MysqlAdapter) def test_should_set_default_for_mysql_binary_data_types - binary_column = MysqlAdapter::Column.new("title", "a", Type::Value.new, "binary(1)") + binary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "binary(1)") assert_equal "a", binary_column.default - varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Value.new, "varbinary(1)") + varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)") assert_equal "a", varbinary_column.default end def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do - MysqlAdapter::Column.new("title", "a", Type::Value.new, "blob") + MysqlAdapter::Column.new("title", "a", Type::Binary.new, "blob") end assert_raise ArgumentError do - MysqlAdapter::Column.new("title", "Hello", Type::Value.new, "text") + MysqlAdapter::Column.new("title", "Hello", Type::Text.new) end - text_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "text") + text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new) assert_equal nil, text_column.default - not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "text", false) + not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new, "text", false) assert_equal "", not_null_text_column.default end def test_has_default_should_return_false_for_blob_and_text_data_types - blob_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "blob") + blob_column = MysqlAdapter::Column.new("title", nil, Type::Binary.new, "blob") assert !blob_column.has_default? - text_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "text") + text_column = MysqlAdapter::Column.new("title", nil, Type::Text.new) assert !text_column.has_default? end end if current_adapter?(:Mysql2Adapter) def test_should_set_default_for_mysql_binary_data_types - binary_column = Mysql2Adapter::Column.new("title", "a", Type::Value.new, "binary(1)") + binary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "binary(1)") assert_equal "a", binary_column.default - varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Value.new, "varbinary(1)") + varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "varbinary(1)") assert_equal "a", varbinary_column.default end def test_should_not_set_default_for_blob_and_text_data_types assert_raise ArgumentError do - Mysql2Adapter::Column.new("title", "a", Type::Value.new, "blob") + Mysql2Adapter::Column.new("title", "a", Type::Binary.new, "blob") end assert_raise ArgumentError do - Mysql2Adapter::Column.new("title", "Hello", Type::Value.new, "text") + Mysql2Adapter::Column.new("title", "Hello", Type::Text.new) end - text_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "text") + text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new) assert_equal nil, text_column.default - not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "text", false) + not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new, "text", false) assert_equal "", not_null_text_column.default end def test_has_default_should_return_false_for_blob_and_text_data_types - blob_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "blob") + blob_column = Mysql2Adapter::Column.new("title", nil, Type::Binary.new, "blob") assert !blob_column.has_default? - text_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "text") + text_column = Mysql2Adapter::Column.new("title", nil, Type::Text.new) assert !text_column.has_default? end end diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb index c2135d75da..91605a52f9 100644 --- a/activerecord/test/cases/column_test.rb +++ b/activerecord/test/cases/column_test.rb @@ -5,7 +5,7 @@ module ActiveRecord module ConnectionAdapters class ColumnTest < ActiveRecord::TestCase def test_type_cast_boolean - column = Column.new("field", nil, Type::Value.new, "boolean") + column = Column.new("field", nil, Type::Boolean.new) assert column.type_cast('').nil? assert column.type_cast(nil).nil? @@ -36,14 +36,14 @@ module ActiveRecord end def test_type_cast_string - column = Column.new("field", nil, Type::Value.new, "varchar") + column = Column.new("field", nil, Type::String.new) assert_equal "1", column.type_cast(true) assert_equal "0", column.type_cast(false) assert_equal "123", column.type_cast(123) end def test_type_cast_integer - column = Column.new("field", nil, Type::Value.new, "integer") + column = Column.new("field", nil, Type::Integer.new) assert_equal 1, column.type_cast(1) assert_equal 1, column.type_cast('1') assert_equal 1, column.type_cast('1ignore') @@ -56,50 +56,50 @@ module ActiveRecord end def test_type_cast_non_integer_to_integer - column = Column.new("field", nil, Type::Value.new, "integer") + column = Column.new("field", nil, Type::Integer.new) assert_nil column.type_cast([1,2]) assert_nil column.type_cast({1 => 2}) assert_nil column.type_cast((1..2)) end def test_type_cast_activerecord_to_integer - column = Column.new("field", nil, Type::Value.new, "integer") + column = Column.new("field", nil, Type::Integer.new) firm = Firm.create(:name => 'Apple') assert_nil column.type_cast(firm) end def test_type_cast_object_without_to_i_to_integer - column = Column.new("field", nil, Type::Value.new, "integer") + column = Column.new("field", nil, Type::Integer.new) assert_nil column.type_cast(Object.new) end def test_type_cast_nan_and_infinity_to_integer - column = Column.new("field", nil, Type::Value.new, "integer") + column = Column.new("field", nil, Type::Integer.new) assert_nil column.type_cast(Float::NAN) assert_nil column.type_cast(1.0/0.0) end def test_type_cast_float - column = Column.new("field", nil, Type::Value.new, "float") + column = Column.new("field", nil, Type::Float.new) assert_equal 1.0, column.type_cast("1") end def test_type_cast_decimal - column = Column.new("field", nil, Type::Value.new, "decimal") + column = Column.new("field", nil, Type::Decimal.new) assert_equal BigDecimal.new("0"), column.type_cast(BigDecimal.new("0")) assert_equal BigDecimal.new("123"), column.type_cast(123.0) assert_equal BigDecimal.new("1"), column.type_cast(:"1") end def test_type_cast_binary - column = Column.new("field", nil, Type::Value.new, "binary") + column = Column.new("field", nil, Type::Binary.new) assert_equal nil, column.type_cast(nil) assert_equal "1", column.type_cast("1") assert_equal 1, column.type_cast(1) end def test_type_cast_time - column = Column.new("field", nil, Type::Value.new, "time") + column = Column.new("field", nil, Type::Time.new) assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast('') assert_equal nil, column.type_cast('ABC') @@ -109,7 +109,11 @@ module ActiveRecord end def test_type_cast_datetime_and_timestamp - [Column.new("field", nil, Type::Value.new, "datetime"), Column.new("field", nil, Type::Value.new, "timestamp")].each do |column| + columns = [ + Column.new("field", nil, Type::DateTime.new), + Column.new("field", nil, Type::Timestamp.new), + ] + columns.each do |column| assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast('') assert_equal nil, column.type_cast(' ') @@ -121,7 +125,7 @@ module ActiveRecord end def test_type_cast_date - column = Column.new("field", nil, Type::Value.new, "date") + column = Column.new("field", nil, Type::Date.new) assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast('') assert_equal nil, column.type_cast(' ') @@ -132,7 +136,7 @@ module ActiveRecord end def test_type_cast_duration_to_integer - column = Column.new("field", nil, Type::Value.new, "integer") + column = Column.new("field", nil, Type::Integer.new) assert_equal 1800, column.type_cast(30.minutes) assert_equal 7200, column.type_cast(2.hours) end @@ -147,7 +151,7 @@ module ActiveRecord if current_adapter?(:SQLite3Adapter) def test_binary_encoding - column = SQLite3Column.new("field", nil, Type::Value.new, "binary") + column = SQLite3Column.new("field", nil, Type::Binary.new) utf8_string = "a string".encode(Encoding::UTF_8) type_cast = column.type_cast(utf8_string) diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb new file mode 100644 index 0000000000..d4d67487db --- /dev/null +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -0,0 +1,61 @@ +require "cases/helper" + +if current_adapter?(:MysqlAdapter, :Mysql2Adapter) +module ActiveRecord + module ConnectionAdapters + class MysqlTypeLookupTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + end + + def test_boolean_types + emulate_booleans(true) do + assert_lookup_type :boolean, 'tinyint(1)' + assert_lookup_type :boolean, 'TINYINT(1)' + end + end + + def test_string_types + assert_lookup_type :string, "enum('one', 'two', 'three')" + assert_lookup_type :string, "ENUM('one', 'two', 'three')" + assert_lookup_type :string, "set('one', 'two', 'three')" + assert_lookup_type :string, "SET('one', 'two', 'three')" + end + + def test_binary_types + assert_lookup_type :binary, 'bit' + assert_lookup_type :binary, 'BIT' + end + + def test_integer_types + emulate_booleans(false) do + assert_lookup_type :integer, 'tinyint(1)' + assert_lookup_type :integer, 'TINYINT(1)' + assert_lookup_type :integer, 'year' + assert_lookup_type :integer, 'YEAR' + end + end + + private + + def assert_lookup_type(type, lookup) + cast_type = @connection.type_map.lookup(lookup) + assert_equal type, cast_type.type + end + + def emulate_booleans(value) + old_emulate_booleans = @connection.emulate_booleans + change_emulate_booleans(value) + yield + ensure + change_emulate_booleans(old_emulate_booleans) + end + + def change_emulate_booleans(value) + @connection.emulate_booleans = value + @connection.clear_cache! + end + end + end +end +end diff --git a/activerecord/test/cases/connection_adapters/type/type_map_test.rb b/activerecord/test/cases/connection_adapters/type/type_map_test.rb new file mode 100644 index 0000000000..565f44eef8 --- /dev/null +++ b/activerecord/test/cases/connection_adapters/type/type_map_test.rb @@ -0,0 +1,102 @@ +require "cases/helper" + +module ActiveRecord + module ConnectionAdapters + module Type + class TypeMapTest < ActiveRecord::TestCase + def test_default_type + mapping = TypeMap.new + + assert_kind_of Value, mapping.lookup(:undefined) + end + + def test_registering_types + boolean = Boolean.new + mapping = TypeMap.new + + mapping.register_type(/boolean/i, boolean) + + assert_equal mapping.lookup('boolean'), boolean + end + + def test_overriding_registered_types + time = Time.new + timestamp = Timestamp.new + mapping = TypeMap.new + + mapping.register_type(/time/i, time) + mapping.register_type(/time/i, timestamp) + + assert_equal mapping.lookup('time'), timestamp + end + + def test_fuzzy_lookup + string = String.new + mapping = TypeMap.new + + mapping.register_type(/varchar/i, string) + + assert_equal mapping.lookup('varchar(20)'), string + end + + def test_aliasing_types + string = String.new + mapping = TypeMap.new + + mapping.register_type(/string/i, string) + mapping.alias_type(/varchar/i, 'string') + + assert_equal mapping.lookup('varchar'), string + end + + def test_changing_type_changes_aliases + time = Time.new + timestamp = Timestamp.new + mapping = TypeMap.new + + mapping.register_type(/timestamp/i, time) + mapping.alias_type(/datetime/i, 'timestamp') + mapping.register_type(/timestamp/i, timestamp) + + assert_equal mapping.lookup('datetime'), timestamp + end + + def test_aliases_keep_metadata + mapping = TypeMap.new + + mapping.register_type(/decimal/i) { |sql_type| sql_type } + mapping.alias_type(/number/i, 'decimal') + + assert_equal mapping.lookup('number(20)'), 'decimal(20)' + assert_equal mapping.lookup('number'), 'decimal' + end + + def test_register_proc + string = String.new + binary = Binary.new + mapping = TypeMap.new + + mapping.register_type(/varchar/i) do |type| + if type.include?('(') + string + else + binary + end + end + + assert_equal mapping.lookup('varchar(20)'), string + assert_equal mapping.lookup('varchar'), binary + end + + def test_requires_value_or_block + mapping = TypeMap.new + + assert_raises(ArgumentError) do + mapping.register_type(/only key/i) + end + end + end + end + end +end + diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb new file mode 100644 index 0000000000..a5b01260d3 --- /dev/null +++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb @@ -0,0 +1,100 @@ +require "cases/helper" + +unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strigns for lookup +module ActiveRecord + module ConnectionAdapters + class TypeLookupTest < ActiveRecord::TestCase + setup do + @connection = ActiveRecord::Base.connection + end + + def test_boolean_types + assert_lookup_type :boolean, 'boolean' + assert_lookup_type :boolean, 'BOOLEAN' + end + + def test_string_types + assert_lookup_type :string, 'char' + assert_lookup_type :string, 'varchar' + assert_lookup_type :string, 'VARCHAR' + assert_lookup_type :string, 'varchar(255)' + assert_lookup_type :string, 'character varying' + end + + def test_binary_types + assert_lookup_type :binary, 'binary' + assert_lookup_type :binary, 'BINARY' + assert_lookup_type :binary, 'blob' + assert_lookup_type :binary, 'BLOB' + end + + def test_text_types + assert_lookup_type :text, 'text' + assert_lookup_type :text, 'TEXT' + assert_lookup_type :text, 'clob' + assert_lookup_type :text, 'CLOB' + end + + def test_date_types + assert_lookup_type :date, 'date' + assert_lookup_type :date, 'DATE' + end + + def test_time_types + assert_lookup_type :time, 'time' + assert_lookup_type :time, 'TIME' + end + + def test_timestamp_types + assert_lookup_type :timestamp, 'timestamp' + assert_lookup_type :timestamp, 'TIMESTAMP' + end + + def test_datetime_types + assert_lookup_type :datetime, 'datetime' + assert_lookup_type :datetime, 'DATETIME' + end + + def test_decimal_types + assert_lookup_type :decimal, 'decimal' + assert_lookup_type :decimal, 'decimal(2,8)' + assert_lookup_type :decimal, 'DECIMAL' + assert_lookup_type :decimal, 'numeric' + assert_lookup_type :decimal, 'numeric(2,8)' + assert_lookup_type :decimal, 'NUMERIC' + assert_lookup_type :decimal, 'number' + assert_lookup_type :decimal, 'number(2,8)' + assert_lookup_type :decimal, 'NUMBER' + end + + def test_float_types + assert_lookup_type :float, 'float' + assert_lookup_type :float, 'FLOAT' + assert_lookup_type :float, 'double' + assert_lookup_type :float, 'DOUBLE' + end + + def test_integer_types + assert_lookup_type :integer, 'integer' + assert_lookup_type :integer, 'INTEGER' + assert_lookup_type :integer, 'tinyint' + assert_lookup_type :integer, 'smallint' + assert_lookup_type :integer, 'bigint' + assert_lookup_type :integer, 'decimal(2)' + assert_lookup_type :integer, 'decimal(2,0)' + assert_lookup_type :integer, 'numeric(2)' + assert_lookup_type :integer, 'numeric(2,0)' + assert_lookup_type :integer, 'number(2)' + assert_lookup_type :integer, 'number(2,0)' + end + + private + + def assert_lookup_type(type, lookup) + cast_type = @connection.type_map.lookup(lookup) + assert_equal type, cast_type.type + end + end + end +end +end diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 2d7a7ec73a..a7c287515d 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -274,6 +274,16 @@ module ActiveRecord ensure connection.drop_table(:my_table) rescue nil end + + def test_column_with_index + connection.create_table "my_table", force: true do |t| + t.string :item_number, index: true + end + + assert connection.index_exists?("my_table", :item_number, name: :index_my_table_on_item_number) + ensure + connection.drop_table(:my_table) rescue nil + end end end end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 0a614c3bfd..5bd2f00129 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -76,12 +76,6 @@ class AuditLog < ActiveRecord::Base belongs_to :unvalidated_developer, :class_name => 'Developer' end -DeveloperSalary = Struct.new(:amount) -class DeveloperWithAggregate < ActiveRecord::Base - self.table_name = 'developers' - composed_of :salary, :class_name => 'DeveloperSalary', :mapping => [%w(salary amount)] -end - class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base self.table_name = 'developers' has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id' |