Skip to content

Commit

Permalink
r3616@asus: jeremy | 2005-09-26 23:09:28 -0700
Browse files Browse the repository at this point in the history
 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
  • Loading branch information
jeremy committed Oct 16, 2005
1 parent 22b77da commit 7117fdb
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 42 deletions.
8 changes: 4 additions & 4 deletions activerecord/lib/active_record/base.rb
Expand Up @@ -625,9 +625,9 @@ def inheritance_column
"type" "type"
end end


# Defines the sequence_name (for Oracle) -- can be overridden in subclasses. # Default sequence_name. Use set_sequence_name to override.
def sequence_name def sequence_name
"#{table_name}_seq" connection.default_sequence_name(table_name, primary_key)
end end


# Sets the table name to use to the given value, or (if the value # Sets the table name to use to the given value, or (if the value
Expand Down Expand Up @@ -675,8 +675,8 @@ def set_inheritance_column( value=nil, &block )


# Sets the name of the sequence to use when generating ids to the given # 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 # 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 # given block. This is required for Oracle and is useful for any
# explicit sequences. # database which relies on sequences for primary key generation.
# #
# Setting the sequence name when using other dbs will have no effect. # 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 # If a sequence name is not explicitly set when using Oracle, it will
Expand Down
Expand Up @@ -114,9 +114,15 @@ def self.remove_connection(klass=self)


# Set the connection for the class. # Set the connection for the class.
def self.connection=(spec) def self.connection=(spec)
raise ConnectionNotEstablished unless spec if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
conn = self.send(spec.adapter_method, spec.config) active_connections[self] = spec
active_connections[self] = conn 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 end
end end
Expand Up @@ -90,6 +90,15 @@ def add_limit_offset!(sql, options)
end end
end 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 end
end end
Expand Up @@ -99,6 +99,10 @@ def guess_date_or_time(value)
# * <tt>:password</tt> -- Defaults to nothing # * <tt>:password</tt> -- Defaults to nothing
# * <tt>:host</tt> -- Defaults to localhost # * <tt>:host</tt> -- Defaults to localhost
class OCIAdapter < AbstractAdapter class OCIAdapter < AbstractAdapter
def default_sequence_name(table, column)
"#{table}_seq"
end

def quote_string(string) def quote_string(string)
string.gsub(/'/, "''") string.gsub(/'/, "''")
end end
Expand Down
Expand Up @@ -102,7 +102,7 @@ def select_one(sql, name = nil) #:nodoc:
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
execute(sql, name) execute(sql, name)
table = sql.split(" ", 4)[2] table = sql.split(" ", 4)[2]
id_value || last_insert_id(table, pk) id_value || last_insert_id(table, sequence_name)
end end


def query(sql, name = nil) #:nodoc: def query(sql, name = nil) #:nodoc:
Expand Down Expand Up @@ -198,6 +198,37 @@ def schema_search_path=(schema_csv) #:nodoc:
def schema_search_path #:nodoc: def schema_search_path #:nodoc:
@schema_search_path ||= query('SHOW search_path')[0][0] @schema_search_path ||= query('SHOW search_path')[0][0]
end 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) def rename_table(name, new_name)
execute "ALTER TABLE #{name} RENAME TO #{new_name}" execute "ALTER TABLE #{name} RENAME TO #{new_name}"
Expand Down Expand Up @@ -242,9 +273,10 @@ def remove_index(table_name, options) #:nodoc:
private private
BYTEA_COLUMN_TYPE_OID = 17 BYTEA_COLUMN_TYPE_OID = 17


def last_insert_id(table, column = nil) def last_insert_id(table, sequence_name)
sequence_name = "#{table}_#{column || 'id'}_seq" if sequence_name
@connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i @connection.exec("SELECT currval('#{sequence_name}')")[0][0].to_i
end
end end


def select(sql, name = nil) def select(sql, name = nil)
Expand Down
24 changes: 7 additions & 17 deletions activerecord/lib/active_record/fixtures.rb
Expand Up @@ -257,30 +257,20 @@ def self.create_fixtures(fixtures_directory, *table_names)
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures } fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
fixtures.each { |fixture| fixture.insert_fixtures } fixtures.each { |fixture| fixture.insert_fixtures }
end 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 return fixtures.size > 1 ? fixtures : fixtures.first
ensure ensure
ActiveRecord::Base.logger.level = old_logger_level ActiveRecord::Base.logger.level = old_logger_level
end end
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 attr_reader :table_name


Expand Down
17 changes: 9 additions & 8 deletions activerecord/lib/active_record/query_cache.rb
Expand Up @@ -44,14 +44,15 @@ def method_missing(method, *arguments, &proc)


class Base class Base
# Set the connection for the class with caching on # Set the connection for the class with caching on
def self.connection=(spec) class << self
raise ConnectionNotEstablished unless spec alias_method :connection_without_query_cache=, :connection=


conn = spec.config[:query_cache] ? def connection=(spec)
QueryCache.new(self.send(spec.adapter_method, spec.config)) : if spec.is_a?(ConnectionSpecification) and spec.config[:query_cache]
self.send(spec.adapter_method, spec.config) spec = QueryCache.new(self.send(spec.adapter_method, spec.config))

end
active_connections[self] = conn self.connection_without_query_cache = spec
end
end end
end end


Expand Down
7 changes: 5 additions & 2 deletions activerecord/test/associations_test.rb
Expand Up @@ -710,7 +710,7 @@ def test_assign_ids


class BelongsToAssociationsTest < Test::Unit::TestCase class BelongsToAssociationsTest < Test::Unit::TestCase
fixtures :accounts, :companies, :developers, :projects, :topics, fixtures :accounts, :companies, :developers, :projects, :topics,
:developers_projects :developers_projects, :computers, :authors, :posts


def test_belongs_to def test_belongs_to
Client.find(3).firm.name Client.find(3).firm.name
Expand Down Expand Up @@ -832,7 +832,7 @@ def test_forgetting_the_load_when_foreign_key_enters_late
end end


def test_field_name_same_as_foreign_key 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" # ' assert_not_nil computer.developer, ":foreign key == attribute didn't lock up" # '
end end


Expand Down Expand Up @@ -939,7 +939,10 @@ def test_store_association_in_two_relations_with_one_save_in_existing_object_wit


def test_association_assignment_sticks def test_association_assignment_sticks
post = Post.find(:first) post = Post.find(:first)

author1, author2 = Author.find(:all, :limit => 2) author1, author2 = Author.find(:all, :limit => 2)
assert_not_nil author1
assert_not_nil author2


# make sure the association is loaded # make sure the association is loaded
post.author post.author
Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/base_test.rb
Expand Up @@ -553,7 +553,7 @@ def test_mass_assignment_protection_on_defaults
end end


def test_mass_assignment_accessible 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 reply.save


assert reply.approved? assert reply.approved?
Expand Down
@@ -1,5 +1,6 @@
DROP TABLE accounts; DROP TABLE accounts;
DROP TABLE companies; DROP TABLE companies;
DROP SEQUENCE companies_nonstd_seq;
DROP TABLE topics; DROP TABLE topics;
DROP TABLE developers; DROP TABLE developers;
DROP TABLE projects; DROP TABLE projects;
Expand Down
5 changes: 3 additions & 2 deletions activerecord/test/fixtures/db_definitions/postgresql.sql
Expand Up @@ -6,8 +6,10 @@ CREATE TABLE accounts (
); );
SELECT setval('accounts_id_seq', 100); SELECT setval('accounts_id_seq', 100);


CREATE SEQUENCE companies_nonstd_seq START 101;

CREATE TABLE companies ( CREATE TABLE companies (
id serial, id integer DEFAULT nextval('companies_nonstd_seq'),
"type" character varying(50), "type" character varying(50),
"ruby_type" character varying(50), "ruby_type" character varying(50),
firm_id integer, firm_id integer,
Expand All @@ -16,7 +18,6 @@ CREATE TABLE companies (
rating integer default 1, rating integer default 1,
PRIMARY KEY (id) PRIMARY KEY (id)
); );
SELECT setval('companies_id_seq', 100);


CREATE TABLE developers_projects ( CREATE TABLE developers_projects (
developer_id integer NOT NULL, developer_id integer NOT NULL,
Expand Down
24 changes: 24 additions & 0 deletions activerecord/test/fixtures_test.rb
Expand Up @@ -168,6 +168,30 @@ def test_omap_fixtures
end end
end 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 end




Expand Down
2 changes: 1 addition & 1 deletion activerecord/test/pk_test.rb
Expand Up @@ -53,7 +53,7 @@ def test_string_key
subscriber.id = "jdoe" subscriber.id = "jdoe"
assert_equal("jdoe", subscriber.id) assert_equal("jdoe", subscriber.id)
subscriber.name = "John Doe" subscriber.name = "John Doe"
assert_nothing_raised{ subscriber.save } assert_nothing_raised { subscriber.save! }


subscriberReloaded = Subscriber.find("jdoe") subscriberReloaded = Subscriber.find("jdoe")
assert_equal("John Doe", subscriberReloaded.name) assert_equal("John Doe", subscriberReloaded.name)
Expand Down

0 comments on commit 7117fdb

Please sign in to comment.