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/restoring.sql b/sql/restoring.sql index 86aa5dcfb93..517df274182 100644 --- a/sql/restoring.sql +++ b/sql/restoring.sql @@ -23,7 +23,17 @@ CREATE OR REPLACE FUNCTION @extschema@.timescaledb_post_restore() RETURNS BOOL A $BODY$ DECLARE db text; + catalog_version text; BEGIN + SELECT m.value INTO catalog_version FROM pg_extension x + JOIN _timescaledb_catalog.metadata m ON m.key='timescaledb_version' + WHERE x.extname='timescaledb' AND x.extversion <> m.value; + + -- check that a loaded dump is compatible with the currently running code + IF FOUND THEN + RAISE EXCEPTION 'catalog version mismatch, expected "%" seen "%"', '@PROJECT_VERSION_MOD@', catalog_version; + END IF; + SELECT current_database() INTO db; EXECUTE format($$ALTER DATABASE %I RESET timescaledb.restoring $$, db); -- we cannot use reset here because the reset_val might not be off diff --git a/sql/updates/version_check.sql b/sql/updates/version_check.sql new file mode 100644 index 00000000000..f6a77bba205 --- /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..a86be701899 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,29 @@ 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) + +-- test version check in post_restore +\c :TEST_DBNAME :ROLE_SUPERUSER +UPDATE _timescaledb_catalog.metadata SET value = '1.2.3' WHERE key = 'timescaledb_version'; +\set ON_ERROR_STOP 0 +-- set verbosity to sqlstate to suppress version dependant error message +\set VERBOSITY sqlstate +SELECT timescaledb_post_restore(); +ERROR: P0001 +\set ON_ERROR_STOP 1 +UPDATE _timescaledb_catalog.metadata m SET value = x.extversion FROM pg_extension x WHERE m.key = 'timescaledb_version' AND x.extname='timescaledb'; +SELECT timescaledb_post_restore(); + timescaledb_post_restore +-------------------------- + t +(1 row) + diff --git a/test/sql/metadata.sql b/test/sql/metadata.sql index f3c172356de..05f56b4f271 100644 --- a/test/sql/metadata.sql +++ b/test/sql/metadata.sql @@ -52,3 +52,23 @@ 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'; + +-- test version check in post_restore +\c :TEST_DBNAME :ROLE_SUPERUSER +UPDATE _timescaledb_catalog.metadata SET value = '1.2.3' WHERE key = 'timescaledb_version'; +\set ON_ERROR_STOP 0 +-- set verbosity to sqlstate to suppress version dependant error message +\set VERBOSITY sqlstate +SELECT timescaledb_post_restore(); +\set ON_ERROR_STOP 1 + +UPDATE _timescaledb_catalog.metadata m SET value = x.extversion FROM pg_extension x WHERE m.key = 'timescaledb_version' AND x.extname='timescaledb'; +SELECT timescaledb_post_restore(); + + 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(); -