diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2005-10-16 03:45:39 +0000 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2005-10-16 03:45:39 +0000 |
commit | 7117fdb8ceeb4d8cc18651a74ea52f1bed2b077c (patch) | |
tree | 20dff4d44de2dcd1401e8cf008ceca0f077d50d8 | |
parent | 22b77daeee65aa80061f247a3b36404bacd7dff5 (diff) | |
download | rails-7117fdb8ceeb4d8cc18651a74ea52f1bed2b077c.tar.gz rails-7117fdb8ceeb4d8cc18651a74ea52f1bed2b077c.tar.bz2 rails-7117fdb8ceeb4d8cc18651a74ea52f1bed2b077c.zip |
r3616@asus: jeremy | 2005-09-26 23:09:28 -0700
Ticket 2292 - Sequences, schemas, and fixtures
r3917@asus: jeremy | 2005-10-15 10:43:24 -0700
fix pk assert
r3918@asus: jeremy | 2005-10-15 10:46:52 -0700
rework query cache connection= override
r3919@asus: jeremy | 2005-10-15 10:47:45 -0700
correct fixtures usage
r3920@asus: jeremy | 2005-10-15 10:53:23 -0700
correct attr assignment
r3921@asus: jeremy | 2005-10-15 12:59:10 -0700
sequences
r3922@asus: jeremy | 2005-10-15 16:36:09 -0700
reset fixtures work with sequences
r3951@asus: jeremy | 2005-10-15 23:23:12 -0700
cut down excess features
r3952@asus: jeremy | 2005-10-15 23:40:30 -0700
don't test for PostgreSQL specifically
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2639 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
13 files changed, 113 insertions, 42 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 48d9c6ae12..58f884595c 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -625,9 +625,9 @@ module ActiveRecord #:nodoc: "type" end - # Defines the sequence_name (for Oracle) -- can be overridden in subclasses. + # Default sequence_name. Use set_sequence_name to override. def sequence_name - "#{table_name}_seq" + connection.default_sequence_name(table_name, primary_key) end # Sets the table name to use to the given value, or (if the value @@ -675,8 +675,8 @@ module ActiveRecord #:nodoc: # Sets the name of the sequence to use when generating ids to the given # value, or (if the value is nil or false) to the value returned by the - # given block. Currently useful only when using Oracle, which requires - # explicit sequences. + # given block. This is required for Oracle and is useful for any + # database which relies on sequences for primary key generation. # # Setting the sequence name when using other dbs will have no effect. # If a sequence name is not explicitly set when using Oracle, it will diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index b5ddef32af..eab61085e3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -114,9 +114,15 @@ module ActiveRecord # Set the connection for the class. def self.connection=(spec) - raise ConnectionNotEstablished unless spec - conn = self.send(spec.adapter_method, spec.config) - active_connections[self] = conn + if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter) + active_connections[self] = spec + elsif spec.kind_of?(ConnectionSpecification) + self.connection = self.send(spec.adapter_method, spec.config) + elsif spec.nil? + raise ConnectionNotEstablished + else + establish_connection spec + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 90dc951b6d..c45454eacc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -90,6 +90,15 @@ module ActiveRecord end end end + + def default_sequence_name(table, column) + nil + end + + # Set the sequence to the max value of the table's column. + def reset_sequence!(table, column, sequence = nil) + # Do nothing by default. Implement for PostgreSQL, Oracle, ... + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/oci_adapter.rb b/activerecord/lib/active_record/connection_adapters/oci_adapter.rb index 194e5d0062..9897403797 100644 --- a/activerecord/lib/active_record/connection_adapters/oci_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/oci_adapter.rb @@ -99,6 +99,10 @@ begin # * <tt>:password</tt> -- Defaults to nothing # * <tt>:host</tt> -- Defaults to localhost class OCIAdapter < AbstractAdapter + def default_sequence_name(table, column) + "#{table}_seq" + end + def quote_string(string) string.gsub(/'/, "''") end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e7bedaee9e..203d390046 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -102,7 +102,7 @@ module ActiveRecord def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: execute(sql, name) table = sql.split(" ", 4)[2] - id_value || last_insert_id(table, pk) + id_value || last_insert_id(table, sequence_name) end def query(sql, name = nil) #:nodoc: @@ -198,6 +198,37 @@ module ActiveRecord def schema_search_path #:nodoc: @schema_search_path ||= query('SHOW search_path')[0][0] end + + def default_sequence_name(table_name, pk = 'id') + "#{table_name}_#{pk}_seq" + end + + # Set the sequence to the max value of the table's pk. + def reset_pk_sequence!(table) + sequence, pk = sequence_and_pk_for(table) + if sequence and pk + select_value <<-end_sql, 'Reset sequence' + SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false) + end_sql + end + end + + # Find a table's primary key and sequence. + def sequence_and_pk_for(table, column = nil) + execute(<<-end_sql, 'Find pk sequence')[0] + SELECT (name.nspname || '.' || seq.relname) AS sequence, attr.attname AS pk + FROM pg_class seq, pg_attribute attr, pg_depend dep, pg_namespace name, pg_constraint cons + WHERE seq.oid = dep.objid + AND seq.relnamespace = name.oid + AND attr.attrelid = dep.refobjid + AND attr.attnum = dep.refobjsubid + AND attr.attrelid = cons.conrelid + AND attr.attnum = cons.conkey[1] + AND cons.contype = 'p' + AND dep.refobjid = '#{table}'::regclass + end_sql + end + def rename_table(name, new_name) execute "ALTER TABLE #{name} RENAME TO #{new_name}" @@ -242,9 +273,10 @@ module ActiveRecord private BYTEA_COLUMN_TYPE_OID = 17 - def last_insert_id(table, column = nil) - sequence_name = "#{table}_#{column || 'id'}_seq" - @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i + def last_insert_id(table, sequence_name) + if sequence_name + @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i + end end def select(sql, name = nil) diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6ee963951e..ef4757fcee 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -257,8 +257,13 @@ class Fixtures < YAML::Omap fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } fixtures.each { |fixture| fixture.insert_fixtures } end - - reset_sequences(connection, table_names) if connection.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) + + # Cap primary key sequences to max(pk). + if connection.respond_to?(:reset_pk_sequence!) + table_names.flatten.each do |table_name| + connection.reset_pk_sequence!(table_name) + end + end return fixtures.size > 1 ? fixtures : fixtures.first ensure @@ -266,21 +271,6 @@ class Fixtures < YAML::Omap end end - # Start PostgreSQL fixtures at id 1. Skip tables without models - # and models with nonstandard primary keys. - def self.reset_sequences(connection, table_names) - table_names.flatten.each do |table| - if table_class = table.to_s.classify.constantize rescue nil - pk = table_class.columns_hash[table_class.primary_key] - if pk and pk.type == :integer - connection.execute( - "SELECT setval('#{table}_#{pk.name}_seq', (SELECT COALESCE(MAX(#{pk.name}), 0)+1 FROM #{table}), false)", - 'Setting Sequence' - ) - end - end - end - end attr_reader :table_name diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 794caff59c..e79b3e05a7 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -44,14 +44,15 @@ module ActiveRecord class Base # Set the connection for the class with caching on - def self.connection=(spec) - raise ConnectionNotEstablished unless spec - - conn = spec.config[:query_cache] ? - QueryCache.new(self.send(spec.adapter_method, spec.config)) : - self.send(spec.adapter_method, spec.config) - - active_connections[self] = conn + class << self + alias_method :connection_without_query_cache=, :connection= + + def connection=(spec) + if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache] + spec = QueryCache.new(self.send(spec.adapter_method, spec.config)) + end + self.connection_without_query_cache = spec + end end end diff --git a/activerecord/test/associations_test.rb b/activerecord/test/associations_test.rb index bfe797b167..ff687fcd77 100755 --- a/activerecord/test/associations_test.rb +++ b/activerecord/test/associations_test.rb @@ -710,7 +710,7 @@ end class BelongsToAssociationsTest < Test::Unit::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, - :developers_projects + :developers_projects, :computers, :authors, :posts def test_belongs_to Client.find(3).firm.name @@ -832,7 +832,7 @@ class BelongsToAssociationsTest < Test::Unit::TestCase end def test_field_name_same_as_foreign_key - computer = Computer.find 1 + computer = Computer.find(1) assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # ' end @@ -939,7 +939,10 @@ class BelongsToAssociationsTest < Test::Unit::TestCase def test_association_assignment_sticks post = Post.find(:first) + author1, author2 = Author.find(:all, :limit => 2) + assert_not_nil author1 + assert_not_nil author2 # make sure the association is loaded post.author diff --git a/activerecord/test/base_test.rb b/activerecord/test/base_test.rb index 28e7f28f21..ef65b9859c 100755 --- a/activerecord/test/base_test.rb +++ b/activerecord/test/base_test.rb @@ -553,7 +553,7 @@ class BasicsTest < Test::Unit::TestCase end def test_mass_assignment_accessible - reply = Reply.new("title" => "hello", "content" => "world", "approved" => false) + reply = Reply.new("title" => "hello", "content" => "world", "approved" => true) reply.save assert reply.approved? diff --git a/activerecord/test/fixtures/db_definitions/postgresql.drop.sql b/activerecord/test/fixtures/db_definitions/postgresql.drop.sql index d8d41cf974..7919d13a7f 100644 --- a/activerecord/test/fixtures/db_definitions/postgresql.drop.sql +++ b/activerecord/test/fixtures/db_definitions/postgresql.drop.sql @@ -1,5 +1,6 @@ DROP TABLE accounts; DROP TABLE companies; +DROP SEQUENCE companies_nonstd_seq; DROP TABLE topics; DROP TABLE developers; DROP TABLE projects; diff --git a/activerecord/test/fixtures/db_definitions/postgresql.sql b/activerecord/test/fixtures/db_definitions/postgresql.sql index feea20b827..67f3673734 100644 --- a/activerecord/test/fixtures/db_definitions/postgresql.sql +++ b/activerecord/test/fixtures/db_definitions/postgresql.sql @@ -6,8 +6,10 @@ CREATE TABLE accounts ( ); SELECT setval('accounts_id_seq', 100); +CREATE SEQUENCE companies_nonstd_seq START 101; + CREATE TABLE companies ( - id serial, + id integer DEFAULT nextval('companies_nonstd_seq'), "type" character varying(50), "ruby_type" character varying(50), firm_id integer, @@ -16,7 +18,6 @@ CREATE TABLE companies ( rating integer default 1, PRIMARY KEY (id) ); -SELECT setval('companies_id_seq', 100); CREATE TABLE developers_projects ( developer_id integer NOT NULL, diff --git a/activerecord/test/fixtures_test.rb b/activerecord/test/fixtures_test.rb index ae45a9f251..e3a37b99a3 100755 --- a/activerecord/test/fixtures_test.rb +++ b/activerecord/test/fixtures_test.rb @@ -168,6 +168,30 @@ class FixturesTest < Test::Unit::TestCase end end end + + if Account.connection.respond_to?(:reset_pk_sequence!) + def test_resets_to_min_pk + Account.delete_all + Account.connection.reset_pk_sequence!(Account.table_name) + + one = Account.new(:credit_limit => 50) + one.save! + assert_equal 1, one.id + end + + def test_create_fixtures_resets_sequences + # create_fixtures performs reset_pk_sequence! + max_id = create_fixtures('accounts').inject(0) do |max_id, (name, fixture)| + fixture_id = fixture['id'].to_i + fixture_id > max_id ? fixture_id : max_id + end + + # Clone the last fixture to check that it gets the next greatest id. + another = Account.new(:credit_limit => 1200) + another.save! + assert_equal max_id + 1, another.id + end + end end diff --git a/activerecord/test/pk_test.rb b/activerecord/test/pk_test.rb index d77982df1b..0ee27f969b 100644 --- a/activerecord/test/pk_test.rb +++ b/activerecord/test/pk_test.rb @@ -53,7 +53,7 @@ class PrimaryKeysTest < Test::Unit::TestCase subscriber.id = "jdoe" assert_equal("jdoe", subscriber.id) subscriber.name = "John Doe" - assert_nothing_raised{ subscriber.save } + assert_nothing_raised { subscriber.save! } subscriberReloaded = Subscriber.find("jdoe") assert_equal("John Doe", subscriberReloaded.name) |