From f288544c5a85e9f0cf218000cc9efcc387105082 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Tue, 10 Oct 2023 14:42:19 +0200 Subject: [PATCH] Keep track of catalog version This patch stores the current catalog version in an internal table to allow us to verify catalog and code version match. When doing dump/restore people occasionally report very unusual errors and during investigation it is discovered that they loaded a dump from an older version and run it with a later code version. This allows to detect mismatches between installed code version and loaded dump version. The version number in the metadata table will be kept updated in upgrade and downgrade scripts. --- .unreleased/pr_6185 | 1 + sql/CMakeLists.txt | 21 +++++++++++++++++++++ sql/metadata.sql | 6 ++++++ sql/updates/version_check.sql | 14 ++++++++++++++ test/expected/metadata.out | 14 ++++++++++++-- test/sql/metadata.sql | 7 +++++++ tsl/test/shared/sql/build_info.sql | 1 - 7 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 .unreleased/pr_6185 create mode 100644 sql/updates/version_check.sql diff --git a/.unreleased/pr_6185 b/.unreleased/pr_6185 new file mode 100644 index 00000000000..0f47652d542 --- /dev/null +++ b/.unreleased/pr_6185 @@ -0,0 +1 @@ +Implements: #6185 Keep track of catalog version diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 3c962c2e809..e0cd9eb650f 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -140,6 +140,23 @@ function(version_files SRC_FILE_LIST OUTPUT_FILE_LIST) PARENT_SCOPE) endfunction() +# Function to replace @VARIABLE@ in source files, producing an output file in +# the build dir. Unlike the previous function we need to build a versioned file +# for every version we create an update script for. +function(version_check_file SRC_FILE_LIST OUTPUT_FILE_LIST) + set(result "") + foreach(unversioned_file ${SRC_FILE_LIST}) + set(versioned_file ${unversioned_file}-${START_VERSION}) + list(APPEND result ${CMAKE_CURRENT_BINARY_DIR}/${versioned_file}) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${unversioned_file}) + configure_file(${unversioned_file} ${versioned_file} @ONLY) + endif() + endforeach(unversioned_file) + set(${OUTPUT_FILE_LIST} + "${result}" + PARENT_SCOPE) +endfunction() + # Create versioned files (replacing MODULE_PATHNAME) in the build directory of # all our source files version_files("${PRE_UPDATE_FILES}" PRE_UPDATE_FILES_VERSIONED) @@ -250,6 +267,9 @@ foreach(transition_mod_file ${MOD_FILES_LIST}) list(APPEND PRE_FILES ${ORIGIN_MOD_FILE}) endif() + version_check_file("updates/version_check.sql" VERSION_CHECK_VERSIONED) + list(PREPEND PRE_FILES ${VERSION_CHECK_VERSIONED}) + # There might not have been any changes in the modfile, in which case the # modfile need not be present if(EXISTS ${transition_mod_file}) @@ -271,6 +291,7 @@ foreach(transition_mod_file ${MOD_FILES_LIST}) "${PRE_FILES};${PRE_INSTALL_FUNCTION_FILES_VERSIONED};${SOURCE_FILES_VERSIONED};${POST_FILES_PROCESSED}" ${UPDATE_SCRIPT}) endif() + endforeach(transition_mod_file) add_custom_target(sqlupdatescripts ALL DEPENDS ${UPDATE_SCRIPTS}) diff --git a/sql/metadata.sql b/sql/metadata.sql index 07290e138fd..5e064b46136 100644 --- a/sql/metadata.sql +++ b/sql/metadata.sql @@ -12,3 +12,9 @@ INSERT INTO _timescaledb_catalog.metadata SELECT 'uuid', _timescaledb_functions.generate_uuid(), TRUE ON CONFLICT DO NOTHING; INSERT INTO _timescaledb_catalog.metadata SELECT 'install_timestamp', now(), TRUE ON CONFLICT DO NOTHING; + +-- Install catalog version on database installation and upgrade. +-- This allows us to detect catalog mismatches in dump/restore cycle. +INSERT INTO _timescaledb_catalog.metadata (key, value, include_in_telemetry) +SELECT 'timescaledb_version', '@PROJECT_VERSION_MOD@', FALSE ON CONFLICT (key) DO UPDATE SET value = excluded.value; + diff --git a/sql/updates/version_check.sql b/sql/updates/version_check.sql new file mode 100644 index 00000000000..ea17b6189a8 --- /dev/null +++ b/sql/updates/version_check.sql @@ -0,0 +1,14 @@ +-- This file and its contents are licensed under the Apache License 2.0. +-- Please see the included NOTICE for copyright information and +-- LICENSE-APACHE for a copy of the license. + +DO $$ +DECLARE + catalog_version TEXT; +BEGIN + SELECT value INTO catalog_version FROM _timescaledb_catalog.metadata WHERE key='timescaledb_version' AND value <> '@START_VERSION@'; + IF FOUND THEN + RAISE EXCEPTION 'catalog version mismatch, expected "%" seen "%"', '@START_VERSION@', catalog_version; + END IF; +END$$; + diff --git a/test/expected/metadata.out b/test/expected/metadata.out index a38d792fa22..03c3acee68d 100644 --- a/test/expected/metadata.out +++ b/test/expected/metadata.out @@ -13,7 +13,7 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.test_install_timestamp() RETURN SELECT COUNT(*) from _timescaledb_catalog.metadata; count ------- - 2 + 3 (1 row) SELECT _timescaledb_internal.test_uuid() as uuid_1 \gset @@ -90,7 +90,7 @@ ALTER DATABASE :TEST_DBNAME SET timescaledb.restoring='off'; SELECT COUNT(*) FROM _timescaledb_catalog.metadata; count ------- - 3 + 4 (1 row) -- Verify that this is the old exported_uuid @@ -113,3 +113,13 @@ SELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as export f (1 row) +-- check metadata version matches expected value +SELECT x.extversion = m.value AS "version match" +FROM pg_extension x +JOIN _timescaledb_catalog.metadata m ON m.key='timescaledb_version' +WHERE x.extname='timescaledb'; + version match +--------------- + t +(1 row) + diff --git a/test/sql/metadata.sql b/test/sql/metadata.sql index f3c172356de..46bc3dbdf8b 100644 --- a/test/sql/metadata.sql +++ b/test/sql/metadata.sql @@ -52,3 +52,10 @@ SELECT _timescaledb_internal.test_exported_uuid() = :'uuid_ex_1' as exported_uui -- Verify that the uuid and timestamp are new SELECT _timescaledb_internal.test_uuid() = :'uuid_1' as exported_uuids_diff; SELECT _timescaledb_internal.test_install_timestamp() = :'timestamp_1' as exported_uuids_diff; + +-- check metadata version matches expected value +SELECT x.extversion = m.value AS "version match" +FROM pg_extension x +JOIN _timescaledb_catalog.metadata m ON m.key='timescaledb_version' +WHERE x.extname='timescaledb'; + diff --git a/tsl/test/shared/sql/build_info.sql b/tsl/test/shared/sql/build_info.sql index 37227fa1046..b501a021d0e 100644 --- a/tsl/test/shared/sql/build_info.sql +++ b/tsl/test/shared/sql/build_info.sql @@ -8,4 +8,3 @@ SELECT pg_typeof(commit_tag) AS commit_tag_type, length(commit_hash) AS commit_hash_length, pg_typeof(commit_time) AS commit_time_type FROM _timescaledb_functions.get_git_commit(); -