Skip to content

Commit 0110d7b

Browse files
author
Josh Williams
committed
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.
1 parent 8cea8ae commit 0110d7b

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

activerecord/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66

77
*Matthew Draper*
88

9+
* `pk_and_sequence_for` now ensures that only the pg_depend entries
10+
pointing to pg_class, and thus only sequence objects, are considered.
11+
12+
*Josh Williams*
13+
914
* `where.not` adds `references` for `includes` like normal `where` calls do.
1015

1116
Fixes #14406.

activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ def pk_and_sequence_for(table) #:nodoc:
327327
AND attr.attrelid = cons.conrelid
328328
AND attr.attnum = cons.conkey[1]
329329
AND cons.contype = 'p'
330+
AND dep.classid = 'pg_class'::regclass
330331
AND dep.refobjid = '#{quote_table_name(table)}'::regclass
331332
end_sql
332333

activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,51 @@ def test_pk_and_sequence_for_returns_nil_if_table_not_found
176176
assert_nil @connection.pk_and_sequence_for('unobtainium')
177177
end
178178

179+
def test_pk_and_sequence_for_with_collision_pg_class_oid
180+
@connection.exec_query('create table ex(id serial primary key)')
181+
@connection.exec_query('create table ex2(id serial primary key)')
182+
183+
correct_depend_record = [
184+
"'pg_class'::regclass",
185+
"'ex_id_seq'::regclass",
186+
'0',
187+
"'pg_class'::regclass",
188+
"'ex'::regclass",
189+
'1',
190+
"'a'"
191+
]
192+
193+
collision_depend_record = [
194+
"'pg_attrdef'::regclass",
195+
"'ex2_id_seq'::regclass",
196+
'0',
197+
"'pg_class'::regclass",
198+
"'ex'::regclass",
199+
'1',
200+
"'a'"
201+
]
202+
203+
@connection.exec_query(
204+
"DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
205+
)
206+
@connection.exec_query(
207+
"INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})"
208+
)
209+
@connection.exec_query(
210+
"INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})"
211+
)
212+
213+
seq = @connection.pk_and_sequence_for('ex').last
214+
assert_equal 'ex_id_seq', seq
215+
216+
@connection.exec_query(
217+
"DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'"
218+
)
219+
ensure
220+
@connection.exec_query('DROP TABLE IF EXISTS ex')
221+
@connection.exec_query('DROP TABLE IF EXISTS ex2')
222+
end
223+
179224
def test_exec_insert_number
180225
with_example_table do
181226
insert(@connection, 'number' => 10)

0 commit comments

Comments
 (0)