Skip to content

Commit

Permalink
Add update test for repair table script
Browse files Browse the repository at this point in the history
This commit creates an update repair test that breaks a few tables for
pre-2.0 versions to ensure that the repair script actually fixes them.
The integrity check for the update tests already contain a check that
dimension slices are valid, so there is no need to add a test for that.

Fixes timescale#2824
  • Loading branch information
mkindahl committed Jan 20, 2021
1 parent 19d3912 commit d610732
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 10 deletions.
18 changes: 16 additions & 2 deletions scripts/test_update_from_tag.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ UPDATE_VOLUME=$(docker inspect ${CONTAINER_ORIG} --format='{{range .Mounts }}{{.

echo "Executing setup script on container running ${UPDATE_FROM_IMAGE}:${UPDATE_FROM_TAG}"
docker_pgscript ${CONTAINER_ORIG} /src/test/sql/updates/setup.${TEST_VERSION}.sql

docker_pgcmd ${CONTAINER_ORIG} "CHECKPOINT;"

# Remove container but keep volume
Expand All @@ -199,11 +200,24 @@ docker_pgcmd ${CONTAINER_UPDATED} "ALTER EXTENSION timescaledb UPDATE" "postgres
if [[ "${TEST_VERSION}" > "v6" ]] || [[ "${TEST_VERSION}" = "v6" ]]; then
echo "Executing post update scritps"
docker_pgscript ${CONTAINER_UPDATED} /src/test/sql/updates/post.update.sql "single"
docker_pgscript ${CONTAINER_UPDATED} /src/test/sql/updates/post.repair.sql "single"
fi

# Check that there is nothing wrong before taking a backup
echo "Checking that there are no missing dimension slices"
docker_pgscript ${CONTAINER_UPDATED} /src/test/sql/updates/setup.check.sql

echo "Executing setup script on clean"
docker_pgscript ${CONTAINER_CLEAN_RERUN} /src/test/sql/updates/setup.${TEST_VERSION}.sql

# We need to run the post repair script to make sure that the
# constraint is on the clean rerun as well since the setup script can
# remove it.
if [[ "${TEST_VERSION}" > "v6" ]] || [[ "${TEST_VERSION}" = "v6" ]]; then
echo "Executing post repair scripts"
docker_pgscript ${CONTAINER_CLEAN_RERUN} /src/test/sql/updates/post.repair.sql "single"
fi

docker_exec ${CONTAINER_UPDATED} "pg_dump -h localhost -U postgres -Fc single > /tmp/single.sql"
docker_exec ${CONTAINER_UPDATED} "pg_dump -h localhost -U postgres -Fc dn1 > /tmp/dn1.sql"
docker cp ${CONTAINER_UPDATED}:/tmp/single.sql ${TEST_TMPDIR}/single.sql
Expand All @@ -217,12 +231,12 @@ docker cp ${TEST_TMPDIR}/dn1.sql ${CONTAINER_CLEAN_RESTORE}:/tmp/dn1.sql
docker_exec ${CONTAINER_CLEAN_RESTORE} "createdb -h localhost -U postgres single"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE single SET timescaledb.restoring='on'"
docker_exec ${CONTAINER_CLEAN_RESTORE} "pg_restore -h localhost -U postgres -d single /tmp/single.sql"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE single SET timescaledb.restoring='off'"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE single RESET timescaledb.restoring"

# Restore dn1
docker_exec ${CONTAINER_CLEAN_RESTORE} "createdb -h localhost -U postgres dn1"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE dn1 SET timescaledb.restoring='on'"
docker_exec ${CONTAINER_CLEAN_RESTORE} "pg_restore -h localhost -U postgres -d dn1 /tmp/dn1.sql"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE dn1 SET timescaledb.restoring='off'"
docker_pgcmd ${CONTAINER_CLEAN_RESTORE} "ALTER DATABASE dn1 RESET timescaledb.restoring"

docker_pgdiff_all /src/test/sql/updates/post.${TEST_VERSION}.sql "single"
16 changes: 8 additions & 8 deletions sql/updates/2.0.0-rc1--2.0.0-rc2.sql
Original file line number Diff line number Diff line change
Expand Up @@ -137,28 +137,28 @@ SELECT DISTINCT
dimension_id,
CASE
WHEN column_type = 'timestamptz'::regtype THEN
EXTRACT(EPOCH FROM range_start::timestamptz) * 1000000
EXTRACT(EPOCH FROM range_start::timestamptz)::bigint * 1000000
WHEN column_type = 'timestamp'::regtype THEN
EXTRACT(EPOCH FROM range_start::timestamp) * 1000000
EXTRACT(EPOCH FROM range_start::timestamp)::bigint * 1000000
WHEN column_type = 'date'::regtype THEN
EXTRACT(EPOCH FROM range_start::date) * 1000000
EXTRACT(EPOCH FROM range_start::date)::bigint * 1000000
ELSE
CASE
WHEN range_start IS NULL
THEN -9223372036854775808
THEN (-9223372036854775808)::bigint
ELSE range_start::bigint
END
END AS range_start,
CASE
WHEN column_type = 'timestamptz'::regtype THEN
EXTRACT(EPOCH FROM range_end::timestamptz) * 1000000
EXTRACT(EPOCH FROM range_end::timestamptz)::bigint * 1000000
WHEN column_type = 'timestamp'::regtype THEN
EXTRACT(EPOCH FROM range_end::timestamp) * 1000000
EXTRACT(EPOCH FROM range_end::timestamp)::bigint * 1000000
WHEN column_type = 'date'::regtype THEN
EXTRACT(EPOCH FROM range_end::date) * 1000000
EXTRACT(EPOCH FROM range_end::date)::bigint * 1000000
ELSE
CASE WHEN range_end IS NULL
THEN 9223372036854775807
THEN 9223372036854775807::bigint
ELSE range_end::bigint
END
END AS range_end
Expand Down
9 changes: 9 additions & 0 deletions test/sql/updates/post.repair.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- 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.

-- Re-add the dropped foreign key constraint that was dropped for
-- repair testing.
ALTER TABLE _timescaledb_catalog.chunk_constraint
ADD CONSTRAINT chunk_constraint_dimension_slice_id_fkey
FOREIGN KEY (dimension_slice_id) REFERENCES _timescaledb_catalog.dimension_slice (id);
1 change: 1 addition & 0 deletions test/sql/updates/post.update.sql
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ BEGIN
PERFORM create_distributed_hypertable('disthyper', 'time', 'device');
INSERT INTO disthyper VALUES ('2020-12-20 12:18', 1, 27.9);
END IF;

END
$$;
18 changes: 18 additions & 0 deletions test/sql/updates/setup.check.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
\echo **** Missing dimension slices ****
SELECT hypertable_id,
(
SELECT format('%I.%I', schema_name, table_name)::regclass
FROM _timescaledb_catalog.hypertable ht
WHERE ht.id = ch.hypertable_id
) AS hypertable,
chunk_id,
dimension_slice_id,
constraint_name,
attname AS column_name,
pg_get_expr(conbin, conrelid) AS constraint_expr
FROM _timescaledb_catalog.chunk_constraint cc
JOIN _timescaledb_catalog.chunk ch ON cc.chunk_id = ch.id
JOIN pg_constraint ON conname = constraint_name
JOIN pg_namespace ns ON connamespace = ns.oid AND ns.nspname = ch.schema_name
JOIN pg_attribute ON attnum = conkey[1] AND attrelid = conrelid
WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice);
173 changes: 173 additions & 0 deletions test/sql/updates/setup.repair.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
-- 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.

-- Test file to check that the repair script works. It will create a
-- bunch of tables and "break" them by removing dimension slices from
-- the dimension slice table. The repair script should then repair all
-- of them and there should be no dimension slices missing.

SELECT (extversion < '2.0.0' AND extversion > '1.7.2') AS runs_repair_script
FROM pg_extension
WHERE extname = 'timescaledb' \gset

CREATE TABLE repair_test_int(time integer not null, temp float8, tag integer, color integer);
CREATE TABLE repair_test_timestamptz(time timestamptz not null, temp float8, tag integer, color integer);
CREATE TABLE repair_test_timestamp(time timestamp not null, temp float8, tag integer, color integer);
CREATE TABLE repair_test_date(time date not null, temp float8, tag integer, color integer);

SELECT create_hypertable('repair_test_int', 'time', 'tag', 2, chunk_time_interval => '3'::bigint);
SELECT create_hypertable('repair_test_timestamptz', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);
SELECT create_hypertable('repair_test_timestamp', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);
SELECT create_hypertable('repair_test_date', 'time', 'tag', 2, chunk_time_interval => '1 day'::interval);

-- These rows will create four constraints for each table.
INSERT INTO repair_test_int VALUES
(4, 24.3, 1, 1),
(4, 24.3, 2, 1),
(10, 24.3, 2, 1);

INSERT INTO repair_test_timestamptz VALUES
('2020-01-01 10:11:12', 24.3, 1, 1),
('2020-01-01 10:11:13', 24.3, 2, 1),
('2020-01-02 10:11:14', 24.3, 2, 1);

INSERT INTO repair_test_timestamp VALUES
('2020-01-01 10:11:12', 24.3, 1, 1),
('2020-01-01 10:11:13', 24.3, 2, 1),
('2020-01-02 10:11:14', 24.3, 2, 1);

INSERT INTO repair_test_date VALUES
('2020-01-01 10:11:12', 24.3, 1, 1),
('2020-01-01 10:11:13', 24.3, 2, 1),
('2020-01-02 10:11:14', 24.3, 2, 1);

-- We always drop the constraint and restore it in the
-- post.repair.sql.
--
-- This way if there are constraint violations, we will notice them
-- when restoring the contraint.
ALTER TABLE _timescaledb_catalog.chunk_constraint
DROP CONSTRAINT chunk_constraint_dimension_slice_id_fkey;

-- We only break the dimension slice table if there is repair that is
-- going to be done, but we create the tables regardless so that we
-- can compare the databases.

\if :runs_repair_script
CREATE TABLE dimension_slice_copy (LIKE _timescaledb_catalog.dimension_slice);

CREATE VIEW slices AS (
SELECT hypertable_id,
(
SELECT format('%I.%I', schema_name, table_name)::regclass
FROM _timescaledb_catalog.hypertable ht
WHERE ht.id = ch.hypertable_id
) AS hypertable,
chunk_id,
dimension_slice_id,
constraint_name,
attname AS column_name,
pg_get_expr(conbin, conrelid) AS constraint_expr
FROM _timescaledb_catalog.chunk_constraint cc
JOIN _timescaledb_catalog.chunk ch ON cc.chunk_id = ch.id
JOIN pg_constraint ON conname = constraint_name
JOIN pg_namespace ns ON connamespace = ns.oid AND ns.nspname = ch.schema_name
JOIN pg_attribute ON attnum = conkey[1] AND attrelid = conrelid
);

-- Break the first dimension on each table, which is one of the time
-- constraints for the table. These are different depending on the
-- time type for the table.
DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN (
SELECT dimension_slice_id FROM slices
WHERE hypertable = 'repair_test_int'::regclass AND column_name = 'time'
ORDER BY dimension_slice_id LIMIT 1
);

DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN (
SELECT dimension_slice_id FROM slices
WHERE hypertable = 'repair_test_timestamp'::regclass AND column_name = 'time'
ORDER BY dimension_slice_id LIMIT 1
);

DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN (
SELECT dimension_slice_id FROM slices
WHERE hypertable = 'repair_test_timestamptz'::regclass AND column_name = 'time'
ORDER BY dimension_slice_id LIMIT 1
);

DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN (
SELECT dimension_slice_id FROM slices
WHERE hypertable = 'repair_test_date'::regclass AND column_name = 'time'
ORDER BY dimension_slice_id LIMIT 1
);

-- Break the partition constraints on some of the tables. The
-- partition constraints look the same in all tables so we create a
-- mix of tables with just one missing dimension slice and several
-- missing dimension slices.
DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN (
SELECT dimension_slice_id FROM slices
WHERE hypertable = 'repair_test_timestamp'::regclass AND column_name = 'tag'
ORDER BY dimension_slice_id LIMIT 1
);

DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN (
SELECT dimension_slice_id FROM slices
WHERE hypertable = 'repair_test_date'::regclass AND column_name = 'tag'
ORDER BY dimension_slice_id
);

CREATE VIEW unparsed_slices AS
SELECT di.id AS dimension_id,
dimension_slice_id,
constraint_name,
column_type,
column_name,
(SELECT SUBSTRING(constraint_expr, $$>=\s*'?([\w\d\s:+-]+)'?$$)) AS range_start,
(SELECT SUBSTRING(constraint_expr, $$<\s*'?([\w\d\s:+-]+)'?$$)) AS range_end
FROM slices JOIN _timescaledb_catalog.dimension di USING (hypertable_id, column_name);

INSERT INTO dimension_slice_copy
SELECT DISTINCT
dimension_slice_id,
dimension_id,
CASE
WHEN column_type = 'timestamptz'::regtype THEN
EXTRACT(EPOCH FROM range_start::timestamptz)::bigint * 1000000
WHEN column_type = 'timestamp'::regtype THEN
EXTRACT(EPOCH FROM range_start::timestamp)::bigint * 1000000
WHEN column_type = 'date'::regtype THEN
EXTRACT(EPOCH FROM range_start::date)::bigint * 1000000
ELSE
CASE
WHEN range_start IS NULL
THEN (-9223372036854775808)::bigint
ELSE range_start::bigint
END
END AS range_start,
CASE
WHEN column_type = 'timestamptz'::regtype THEN
EXTRACT(EPOCH FROM range_end::timestamptz)::bigint * 1000000
WHEN column_type = 'timestamp'::regtype THEN
EXTRACT(EPOCH FROM range_end::timestamp)::bigint * 1000000
WHEN column_type = 'date'::regtype THEN
EXTRACT(EPOCH FROM range_end::date)::bigint * 1000000
ELSE
CASE WHEN range_end IS NULL
THEN 9223372036854775807::bigint
ELSE range_end::bigint
END
END AS range_end
FROM unparsed_slices
WHERE dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice);

\echo **** Expected repairs ****
SELECT * FROM dimension_slice_copy;

DROP VIEW unparsed_slices;
DROP VIEW slices;
DROP TABLE dimension_slice_copy;
\endif

1 change: 1 addition & 0 deletions test/sql/updates/setup.v6.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@
\ir setup.continuous_aggs.v2.sql
\ir setup.compression.sql
\ir setup.policies.sql
\ir setup.repair.sql
\ir setup.multinode.sql

0 comments on commit d610732

Please sign in to comment.