find_schema_problems(): report problems in a stable order

Several sections had no ORDER BY, so when one section found more than one
problem PostgreSQL could return them in any order (found by new
break-one-thing tests). Every section is now sorted. Output is otherwise
unchanged; no version bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
peter 2026-10-06 12:18:54 +01:00
parent 9032fd3a45
commit 738fe0fe2d

View file

@ -12,7 +12,8 @@
-- meta.find_schema_problems() -- meta.find_schema_problems()
-- -------------------------------------------------------------------------- -- --------------------------------------------------------------------------
-- --
-- Returns one row per problem: (object, problem). Read-only. -- Returns one row per problem: (object, problem), in a stable order
-- (section by section, each sorted). Read-only.
-- --
-- The expectations below mirror 00..08. When any of those files changes, -- The expectations below mirror 00..08. When any of those files changes,
-- update this file in the same commit. The release tests (a freshly -- update this file in the same commit. The release tests (a freshly
@ -42,12 +43,14 @@ BEGIN
RETURN QUERY RETURN QUERY
SELECT 'schema ' || s, 'missing' SELECT 'schema ' || s, 'missing'
FROM unnest(ARRAY['meta', 'series', 'log']) AS s FROM unnest(ARRAY['meta', 'series', 'log']) AS s
WHERE NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = s); WHERE NOT EXISTS (SELECT 1 FROM pg_namespace WHERE nspname = s)
ORDER BY 1;
RETURN QUERY RETURN QUERY
SELECT 'extension ' || e, 'missing' SELECT 'extension ' || e, 'missing'
FROM unnest(ARRAY['pg_trgm']) AS e FROM unnest(ARRAY['pg_trgm']) AS e
WHERE NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = e); WHERE NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = e)
ORDER BY 1;
RETURN QUERY RETURN QUERY
SELECT 'sequence ' || s, 'missing' SELECT 'sequence ' || s, 'missing'
@ -57,7 +60,8 @@ BEGIN
'log.pending_changes_id_seq', 'log.pending_changes_id_seq',
'log.script_log_id_seq' 'log.script_log_id_seq'
]) AS s ]) AS s
WHERE to_regclass(s) IS NULL; WHERE to_regclass(s) IS NULL
ORDER BY 1;
-- Tables and columns ---------------------------------------------------- -- Tables and columns ----------------------------------------------------
RETURN QUERY RETURN QUERY
@ -231,7 +235,8 @@ BEGIN
) AS c(tbl, name) ) AS c(tbl, name)
WHERE to_regclass(c.tbl) IS NOT NULL WHERE to_regclass(c.tbl) IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM pg_constraint k AND NOT EXISTS (SELECT 1 FROM pg_constraint k
WHERE k.conrelid = to_regclass(c.tbl) AND k.conname = c.name); WHERE k.conrelid = to_regclass(c.tbl) AND k.conname = c.name)
ORDER BY 1;
RETURN QUERY RETURN QUERY
SELECT 'index ' || i.idx, 'missing' SELECT 'index ' || i.idx, 'missing'
@ -253,12 +258,14 @@ BEGIN
('log.idx_script_log_series', 'log.script_log'), ('log.idx_script_log_series', 'log.script_log'),
('log.idx_script_log_level', 'log.script_log') ('log.idx_script_log_level', 'log.script_log')
) AS i(idx, tbl) ) AS i(idx, tbl)
WHERE to_regclass(i.tbl) IS NOT NULL AND to_regclass(i.idx) IS NULL; WHERE to_regclass(i.tbl) IS NOT NULL AND to_regclass(i.idx) IS NULL
ORDER BY 1;
RETURN QUERY RETURN QUERY
SELECT 'view ' || v, 'missing' SELECT 'view ' || v, 'missing'
FROM unnest(ARRAY['log.script_log_recent']) AS v FROM unnest(ARRAY['log.script_log_recent']) AS v
WHERE to_regclass(v) IS NULL; WHERE to_regclass(v) IS NULL
ORDER BY 1;
-- Functions (compared by whitespace-insensitive body fingerprint) ------- -- Functions (compared by whitespace-insensitive body fingerprint) -------
RETURN QUERY RETURN QUERY
@ -285,7 +292,8 @@ BEGIN
WHERE NOT EXISTS (SELECT 1 FROM pg_proc p WHERE NOT EXISTS (SELECT 1 FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname || '.' || p.proname = f.name WHERE n.nspname || '.' || p.proname = f.name
AND md5(btrim(regexp_replace(p.prosrc, '\s+', ' ', 'g'))) = f.fingerprint); AND md5(btrim(regexp_replace(p.prosrc, '\s+', ' ', 'g'))) = f.fingerprint)
ORDER BY 1;
-- Drop-protection event trigger ----------------------------------------- -- Drop-protection event trigger -----------------------------------------
RETURN QUERY RETURN QUERY
@ -302,7 +310,8 @@ BEGIN
(CASE WHEN t.evtfoid::regproc::text <> 'meta.prevent_direct_drop' (CASE WHEN t.evtfoid::regproc::text <> 'meta.prevent_direct_drop'
THEN 'calls ' || t.evtfoid::regproc::text || ', expected meta.prevent_direct_drop' END) THEN 'calls ' || t.evtfoid::regproc::text || ', expected meta.prevent_direct_drop' END)
) AS x(p) ) AS x(p)
WHERE t.evtname = 'prevent_series_drops' AND p IS NOT NULL; WHERE t.evtname = 'prevent_series_drops' AND p IS NOT NULL
ORDER BY 2;
-- Reference data and version row ---------------------------------------- -- Reference data and version row ----------------------------------------
RETURN QUERY RETURN QUERY
@ -321,7 +330,8 @@ BEGIN
) AS e(tbl, expected) ) AS e(tbl, expected)
WHERE to_regclass(e.tbl) IS NOT NULL WHERE to_regclass(e.tbl) IS NOT NULL
) AS r ) AS r
WHERE r.actual < r.expected; WHERE r.actual < r.expected
ORDER BY 1;
-- Series schema registry ------------------------------------------------ -- Series schema registry ------------------------------------------------
-- Only REGISTERED schemas are examined; user schemas are never looked at. -- Only REGISTERED schemas are examined; user schemas are never looked at.