From 0110d7b714a6ecc810a38ef5a27b66ec321995e5 Mon Sep 17 00:00:00 2001 From: Josh Williams Date: Tue, 19 Nov 2013 15:50:14 -0500 Subject: [PATCH] Postgres schema: Constrain sequence search classid The pk_an_sequence_for query previously joined against pg_class's oid for rows in pg_depend, but pg_depend's objid may point to other system tables, such as pg_attrdef. If a row in one of those other tables coincidentally has the same oid as an (unrelated) sequence, that sequence name may be returned instead of the real one. This ensures that only the pg_depend entries pointing to pg_class are considered. --- activerecord/CHANGELOG.md | 5 +++ .../postgresql/schema_statements.rb | 1 + .../postgresql/postgresql_adapter_test.rb | 45 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f5b8a3145d..91a6f9e1ff 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -6,6 +6,11 @@ *Matthew Draper* +* `pk_and_sequence_for` now ensures that only the pg_depend entries + pointing to pg_class, and thus only sequence objects, are considered. + + *Josh Williams* + * `where.not` adds `references` for `includes` like normal `where` calls do. Fixes #14406. 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 ae8ede4b42..e0afa989cd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -327,6 +327,7 @@ module ActiveRecord AND attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] AND cons.contype = 'p' + AND dep.classid = 'pg_class'::regclass AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 019406dd84..de7acdf3ab 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -176,6 +176,51 @@ module ActiveRecord assert_nil @connection.pk_and_sequence_for('unobtainium') end + def test_pk_and_sequence_for_with_collision_pg_class_oid + @connection.exec_query('create table ex(id serial primary key)') + @connection.exec_query('create table ex2(id serial primary key)') + + correct_depend_record = [ + "'pg_class'::regclass", + "'ex_id_seq'::regclass", + '0', + "'pg_class'::regclass", + "'ex'::regclass", + '1', + "'a'" + ] + + collision_depend_record = [ + "'pg_attrdef'::regclass", + "'ex2_id_seq'::regclass", + '0', + "'pg_class'::regclass", + "'ex'::regclass", + '1', + "'a'" + ] + + @connection.exec_query( + "DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" + ) + @connection.exec_query( + "INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})" + ) + @connection.exec_query( + "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})" + ) + + seq = @connection.pk_and_sequence_for('ex').last + assert_equal 'ex_id_seq', seq + + @connection.exec_query( + "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" + ) + ensure + @connection.exec_query('DROP TABLE IF EXISTS ex') + @connection.exec_query('DROP TABLE IF EXISTS ex2') + end + def test_exec_insert_number with_example_table do insert(@connection, 'number' => 10)