Schema 1.3.0: meta.find_series_registry_mismatches()
An occasional consistency check (not for connect time) comparing meta.series with the registered series schemas: non-series tables, views, sequences and functions inside a series schema; registered series whose table is missing; series tables without a timestamp column or its BRIN index. Report only; user schemas are never examined. find_schema_problems() fingerprints its body like every other procedure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4c067bc4b9
commit
eb0f93e032
4 changed files with 104 additions and 7 deletions
|
|
@ -9,7 +9,7 @@ timeseries database. Both client libraries target the schema defined here:
|
||||||
Neither project should keep its own copy of the DDL. When the schema changes,
|
Neither project should keep its own copy of the DDL. When the schema changes,
|
||||||
it changes **here**, and both projects re-provision / re-test against it.
|
it changes **here**, and both projects re-provision / re-test against it.
|
||||||
|
|
||||||
Current schema version: **1.2.0** (see [`VERSION`](VERSION); recorded at runtime
|
Current schema version: **1.3.0** (see [`VERSION`](VERSION); recorded at runtime
|
||||||
in the `meta.schema_version` table).
|
in the `meta.schema_version` table).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.2.0
|
1.3.0
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- Schema self-check: meta.find_schema_problems()
|
-- Database self-checks, callable by every client (Python, C++, psql)
|
||||||
-- ============================================================================
|
-- ============================================================================
|
||||||
-- Answers one question for every client (Python, C++, psql):
|
|
||||||
-- is this database complete and as the DDL defines it?
|
|
||||||
--
|
--
|
||||||
-- SELECT * FROM meta.find_schema_problems(); -- zero rows = usable
|
-- meta.find_schema_problems() -- on connect: is the database complete and as
|
||||||
|
-- the DDL defines it? zero rows = usable
|
||||||
|
--
|
||||||
|
-- meta.find_series_registry_mismatches() -- occasional consistency check:
|
||||||
|
-- do the series schemas and meta.series agree? (defined at the end)
|
||||||
|
--
|
||||||
|
-- --------------------------------------------------------------------------
|
||||||
|
-- meta.find_schema_problems()
|
||||||
|
-- --------------------------------------------------------------------------
|
||||||
--
|
--
|
||||||
-- Returns one row per problem: (object, problem). Read-only.
|
-- Returns one row per problem: (object, problem). Read-only.
|
||||||
--
|
--
|
||||||
|
|
@ -268,6 +274,7 @@ BEGIN
|
||||||
('meta.drop_all_series', 'fc404a746d91578860bea0d89b166bc7'),
|
('meta.drop_all_series', 'fc404a746d91578860bea0d89b166bc7'),
|
||||||
('meta.drop_series', '6f2a919de965b87521be68252deee0e2'),
|
('meta.drop_series', '6f2a919de965b87521be68252deee0e2'),
|
||||||
('meta.drop_series_if_exists', 'aa1b6ab64d2d3b9ca19ed9033f129524'),
|
('meta.drop_series_if_exists', 'aa1b6ab64d2d3b9ca19ed9033f129524'),
|
||||||
|
('meta.find_series_registry_mismatches', '6dc9ecd2b7656b91a8b410c78a19fa2b'),
|
||||||
('meta.prevent_direct_drop', '1d38cbad0caba72a5104da42e3030929'),
|
('meta.prevent_direct_drop', '1d38cbad0caba72a5104da42e3030929'),
|
||||||
('meta.read_series_batch', '93ef12652fd22b14d210971b537de736'),
|
('meta.read_series_batch', '93ef12652fd22b14d210971b537de736'),
|
||||||
('meta.read_series_batch_cursor', '2ed7d17f545fdfb9d6af577a051699e1'),
|
('meta.read_series_batch_cursor', '2ed7d17f545fdfb9d6af577a051699e1'),
|
||||||
|
|
@ -337,3 +344,93 @@ BEGIN
|
||||||
END IF;
|
END IF;
|
||||||
END;
|
END;
|
||||||
$function$;
|
$function$;
|
||||||
|
|
||||||
|
|
||||||
|
-- --------------------------------------------------------------------------
|
||||||
|
-- meta.find_series_registry_mismatches()
|
||||||
|
-- --------------------------------------------------------------------------
|
||||||
|
-- Occasional consistency check -- NOT run on connect. Compares the series
|
||||||
|
-- registry (meta.series) with what actually exists in the REGISTERED series
|
||||||
|
-- schemas (meta.series_schemas). User schemas are never examined.
|
||||||
|
--
|
||||||
|
-- SELECT * FROM meta.find_series_registry_mismatches(); -- zero rows = consistent
|
||||||
|
--
|
||||||
|
-- One (object, problem) row per finding:
|
||||||
|
-- * a table, view, materialized view, foreign table or sequence in a
|
||||||
|
-- series schema that is not a registered series (an orphaned series table,
|
||||||
|
-- or a user table in the wrong place) -- harmless to the clients;
|
||||||
|
-- * a function in a series schema -- harmless;
|
||||||
|
-- * a series registered in meta.series whose table does not exist --
|
||||||
|
-- every operation on that series fails;
|
||||||
|
-- * a series table without a "timestamp timestamp without time zone NOT
|
||||||
|
-- NULL" column, or without a BRIN index on it.
|
||||||
|
-- Read-only. It reports; deciding what to do with a finding is up to the user.
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION meta.find_series_registry_mismatches()
|
||||||
|
RETURNS TABLE(object text, problem text)
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
STABLE
|
||||||
|
AS $function$
|
||||||
|
BEGIN
|
||||||
|
-- Relations in series schemas that are not registered series -----------
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT n.nspname || '.' || c.relname,
|
||||||
|
CASE c.relkind
|
||||||
|
WHEN 'r' THEN 'table' WHEN 'p' THEN 'table' WHEN 'v' THEN 'view'
|
||||||
|
WHEN 'm' THEN 'materialized view' WHEN 'f' THEN 'foreign table'
|
||||||
|
ELSE 'sequence' END
|
||||||
|
|| ' in a series schema but not registered in meta.series'
|
||||||
|
FROM pg_class c
|
||||||
|
JOIN pg_namespace n ON n.oid = c.relnamespace
|
||||||
|
JOIN meta.series_schemas r ON r.name = n.nspname
|
||||||
|
WHERE c.relkind IN ('r', 'p', 'v', 'm', 'f', 'S')
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM meta.series m
|
||||||
|
WHERE m.db_schema = n.nspname AND m.table_name = c.relname)
|
||||||
|
ORDER BY 1;
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT n.nspname || '.' || p.proname, 'function in a series schema'
|
||||||
|
FROM pg_proc p
|
||||||
|
JOIN pg_namespace n ON n.oid = p.pronamespace
|
||||||
|
JOIN meta.series_schemas r ON r.name = n.nspname
|
||||||
|
ORDER BY 1;
|
||||||
|
|
||||||
|
-- Registered series whose table is missing ------------------------------
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT m.db_schema || '.' || m.table_name,
|
||||||
|
'registered in meta.series but the table does not exist'
|
||||||
|
FROM meta.series m
|
||||||
|
WHERE to_regclass(format('%I.%I', m.db_schema, m.table_name)) IS NULL
|
||||||
|
ORDER BY 1;
|
||||||
|
|
||||||
|
-- Series tables without a proper timestamp column or its BRIN index -----
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT m.db_schema || '.' || m.table_name,
|
||||||
|
CASE WHEN a.attname IS NULL THEN 'no "timestamp" column'
|
||||||
|
ELSE '"timestamp" is ' || format_type(a.atttypid, a.atttypmod)
|
||||||
|
|| CASE WHEN a.attnotnull THEN ' NOT NULL' ELSE '' END
|
||||||
|
|| ', expected timestamp without time zone NOT NULL' END
|
||||||
|
FROM meta.series m
|
||||||
|
CROSS JOIN LATERAL (SELECT to_regclass(format('%I.%I', m.db_schema, m.table_name)) AS rel) t
|
||||||
|
LEFT JOIN pg_attribute a ON a.attrelid = t.rel AND a.attname = 'timestamp'
|
||||||
|
AND a.attnum > 0 AND NOT a.attisdropped
|
||||||
|
WHERE t.rel IS NOT NULL
|
||||||
|
AND (a.attname IS NULL
|
||||||
|
OR format_type(a.atttypid, a.atttypmod) <> 'timestamp without time zone'
|
||||||
|
OR NOT a.attnotnull)
|
||||||
|
ORDER BY 1;
|
||||||
|
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT m.db_schema || '.' || m.table_name, 'no BRIN index on "timestamp"'
|
||||||
|
FROM meta.series m
|
||||||
|
CROSS JOIN LATERAL (SELECT to_regclass(format('%I.%I', m.db_schema, m.table_name)) AS rel) t
|
||||||
|
JOIN pg_attribute a ON a.attrelid = t.rel AND a.attname = 'timestamp'
|
||||||
|
AND a.attnum > 0 AND NOT a.attisdropped
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_index i
|
||||||
|
JOIN pg_class ic ON ic.oid = i.indexrelid
|
||||||
|
JOIN pg_am am ON am.oid = ic.relam
|
||||||
|
WHERE i.indrelid = t.rel AND am.amname = 'brin' AND i.indkey[0] = a.attnum)
|
||||||
|
ORDER BY 1;
|
||||||
|
END;
|
||||||
|
$function$;
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@
|
||||||
-- they were built for (Python: DB_SCHEMA_VERSION in tsapi_pgdb/_version.py).
|
-- they were built for (Python: DB_SCHEMA_VERSION in tsapi_pgdb/_version.py).
|
||||||
|
|
||||||
INSERT INTO meta.schema_version (version, description)
|
INSERT INTO meta.schema_version (version, description)
|
||||||
VALUES ('1.2.0', 'meta.series_schemas registry; lower-case table name check');
|
VALUES ('1.3.0', 'meta.find_series_registry_mismatches()');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue