diff --git a/scripts/test_update_from_tag.sh b/scripts/test_update_from_tag.sh index cf2068b9a9a..a7200d22792 100755 --- a/scripts/test_update_from_tag.sh +++ b/scripts/test_update_from_tag.sh @@ -76,11 +76,11 @@ docker_exec() { } docker_pgcmd() { - docker_exec $1 "psql -h localhost -U postgres -d single -c \"$2\"" + docker_exec $1 "psql -h localhost -U postgres -d single -v TEST_REPAIR=${TEST_REPAIR} -c \"$2\"" } docker_pgscript() { - docker_exec $1 "psql -h localhost -U postgres -v ON_ERROR_STOP=1 -f $2" + docker_exec $1 "psql -h localhost -U postgres -v TEST_REPAIR=${TEST_REPAIR} -v ON_ERROR_STOP=1 -f $2" } docker_pgtest() { @@ -150,14 +150,28 @@ docker rm -f ${CONTAINER_ORIG} echo "Running update container" docker_run_vol ${CONTAINER_UPDATED} ${UPDATE_VOLUME}:/var/lib/postgresql/data ${UPDATE_TO_IMAGE}:${UPDATE_TO_TAG} -echo "Executing ALTER EXTENSION timescaledb UPDATE" +echo "Executing ALTER EXTENSION timescaledb UPDATE ($UPDATE_FROM_TAG -> $UPDATE_TO_TAG)" docker_pgcmd ${CONTAINER_UPDATED} "ALTER EXTENSION timescaledb UPDATE" docker_exec ${CONTAINER_UPDATED} "pg_dump -h localhost -U postgres -Fc single > /tmp/single.sql" docker cp ${CONTAINER_UPDATED}:/tmp/single.sql ${TEST_TMPDIR}/single.sql + +# 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 +if [[ "${TEST_VERSION}" > "v6" ]] || [[ "${TEST_VERSION}" = "v6" ]]; then + if [[ "${TEST_REPAIR}" = "true" ]]; then + # 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. + echo "Executing post update repair script on clean" + docker_pgscript ${CONTAINER_CLEAN_RERUN} /src/test/sql/updates/post.repair.sql "single" + fi +fi echo "Testing updated vs clean" docker_pgdiff ${CONTAINER_UPDATED} ${CONTAINER_CLEAN_RERUN} /src/test/sql/updates/test-rerun.sql @@ -167,7 +181,7 @@ docker cp ${TEST_TMPDIR}/single.sql ${CONTAINER_CLEAN_RESTORE}:/tmp/single.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" -echo "Testing restored" +echo "Comparing upgraded ($UPDATE_FROM_TAG -> $UPDATE_TO_TAG) with clean install ($UPDATE_TO_TAG)" docker_pgdiff ${CONTAINER_UPDATED} ${CONTAINER_CLEAN_RESTORE} /src/test/sql/updates/post.${TEST_VERSION}.sql diff --git a/scripts/test_updates.sh b/scripts/test_updates.sh index fa95a358636..7e0d5e6ced3 100644 --- a/scripts/test_updates.sh +++ b/scripts/test_updates.sh @@ -15,16 +15,19 @@ PG_VERSION=${PG_VERSION:-9.6.5} # Need 9.6.x version since we are # upgrading the extension from # versions that didn't support PG10. +# This will propagate to the test_update_from_tags.sh script +export TEST_REPAIR + FAILED_TEST= KEEP_TEMP_DIRS=false TEST_UPDATE_FROM_TAGS_EXTRA_ARGS= - +TEST_REPAIR=false FAIL_COUNT=0 # Declare a hash table to keep test names keyed by pid declare -A tests -while getopts "cd" opt; +while getopts "cdr" opt; do case $opt in c) @@ -36,6 +39,10 @@ do KEEP_TEMP_DIRS=true TEST_UPDATE_FROM_TAGS_EXTRA_ARGS="-d" ;; + r) + echo "Breaking dimension slices to test repair part" + TEST_REPAIR=true + ;; esac done diff --git a/scripts/test_updates_pg11.sh b/scripts/test_updates_pg11.sh index c6f9b0b2f45..8d42e4d10d1 100755 --- a/scripts/test_updates_pg11.sh +++ b/scripts/test_updates_pg11.sh @@ -8,7 +8,7 @@ SCRIPT_DIR=$(dirname $0) TAGS="1.1.0-pg11 1.1.1-pg11 1.2.0-pg11 1.2.1-pg11 1.2.2-pg11" TEST_VERSION="v2" -TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh +TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh "$@" EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then exit $EXIT_CODE @@ -18,7 +18,7 @@ fi TAGS="1.3.0-pg11 1.3.1-pg11 1.3.2-pg11 1.4.0-pg11 1.4.1-pg11 1.4.2-pg11" TEST_VERSION="v4" -TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh +TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh "$@" EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then exit $EXIT_CODE @@ -27,7 +27,7 @@ fi TAGS="1.5.0-pg11 1.5.1-pg11 1.6.0-pg11 1.6.1-pg11" TEST_VERSION="v5-pg11" -TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh +TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh "$@" EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then exit $EXIT_CODE @@ -36,7 +36,7 @@ fi TAGS="1.7.0-pg11 1.7.1-pg11 1.7.2-pg11 1.7.3-pg11 1.7.4-pg11" TEST_VERSION="v6-pg11" -TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh +TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh "$@" EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then exit $EXIT_CODE diff --git a/scripts/test_updates_pg12.sh b/scripts/test_updates_pg12.sh index 85214bdf0bd..c62d84039ff 100644 --- a/scripts/test_updates_pg12.sh +++ b/scripts/test_updates_pg12.sh @@ -8,7 +8,7 @@ SCRIPT_DIR=$(dirname $0) TAGS="1.7.0-pg12 1.7.1-pg12 1.7.2-pg12 1.7.3-pg12 1.7.4-pg12" TEST_VERSION="v6-pg12" -TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh +TAGS=$TAGS TEST_VERSION=$TEST_VERSION bash ${SCRIPT_DIR}/test_updates.sh "$@" EXIT_CODE=$? if [ $EXIT_CODE -ne 0 ]; then exit $EXIT_CODE diff --git a/sql/updates/1.7.1--1.7.2.sql b/sql/updates/1.7.1--1.7.2.sql index 14bfa274050..5805d8b16c7 100644 --- a/sql/updates/1.7.1--1.7.2.sql +++ b/sql/updates/1.7.1--1.7.2.sql @@ -14,61 +14,70 @@ RETURNS BIGINT AS '@MODULE_PATHNAME@', 'ts_time_to_internal' LANGUAGE C VOLATILE INSERT INTO _timescaledb_catalog.dimension_slice WITH -- All dimension slices that are mentioned in the chunk_constraint - -- table but are missing from the dimension_slice table. + -- table but are missing from the dimension_slice table. There can + -- be duplicates since several chunk constraints can refer to one + -- dimension slice. missing_slices AS ( - SELECT dimension_slice_id, - constraint_name, - attname AS column_name, - pg_get_expr(conbin, conrelid) AS constraint_expr + SELECT DISTINCT ch.hypertable_id, + di.id as dimension_id, + dimension_slice_id, + constraint_name, + di.column_type, + 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 + JOIN _timescaledb_catalog.dimension di + ON ch.hypertable_id = di.hypertable_id AND di.column_name = attname WHERE - dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice) + dimension_slice_id NOT IN (SELECT id FROM _timescaledb_catalog.dimension_slice) ), -- Unparsed range start and end for each dimension slice id that -- is missing. unparsed_missing_slices AS ( - SELECT dimension_slice_id, + SELECT dimension_id, + dimension_slice_id, constraint_name, - column_name, - (SELECT SUBSTRING(constraint_expr, $$>=\s*'?([\d\s:+-]+)'?$$)) AS range_start, - (SELECT SUBSTRING(constraint_expr, $$<\s*'?([\d\s:+-]+)'?$$)) AS range_end - FROM missing_slices + column_type, + column_name, + (SELECT SUBSTRING(constraint_expr, $$>=\s*'?([\d\s:+-]+)'?$$)) AS range_start, + (SELECT SUBSTRING(constraint_expr, $$<\s*'?([\d\s:+-]+)'?$$)) AS range_end + FROM missing_slices ) SELECT dimension_slice_id, - di.id AS dimension_id, + dimension_id, CASE - WHEN di.column_type IN ('smallint'::regtype, 'bigint'::regtype, 'integer'::regtype) THEN - CASE - WHEN range_start IS NULL - THEN -9223372036854775808 - ELSE _timescaledb_internal.time_to_internal(range_start::bigint) - END - WHEN di.column_type = 'timestamptz'::regtype THEN - _timescaledb_internal.time_to_internal(range_start::timestamptz) - WHEN di.column_type = 'timestamp'::regtype THEN - _timescaledb_internal.time_to_internal(range_start::timestamp) - WHEN di.column_type = 'date'::regtype THEN - _timescaledb_internal.time_to_internal(range_start::date) + WHEN column_type IN ('smallint'::regtype, 'bigint'::regtype, 'integer'::regtype) THEN + CASE + WHEN range_start IS NULL + THEN -9223372036854775808 + ELSE _timescaledb_internal.time_to_internal(range_start::bigint) + END + WHEN column_type = 'timestamptz'::regtype THEN + _timescaledb_internal.time_to_internal(range_start::timestamptz) + WHEN column_type = 'timestamp'::regtype THEN + _timescaledb_internal.time_to_internal(range_start::timestamp) + WHEN column_type = 'date'::regtype THEN + _timescaledb_internal.time_to_internal(range_start::date) ELSE - NULL + NULL END AS range_start, - CASE - WHEN di.column_type IN ('smallint'::regtype, 'bigint'::regtype, 'integer'::regtype) THEN - CASE WHEN range_end IS NULL - THEN 9223372036854775807 - ELSE _timescaledb_internal.time_to_internal(range_end::bigint) - END - WHEN di.column_type = 'timestamptz'::regtype THEN - _timescaledb_internal.time_to_internal(range_end::timestamptz) - WHEN di.column_type = 'timestamp'::regtype THEN - _timescaledb_internal.time_to_internal(range_end::timestamp) - WHEN di.column_type = 'date'::regtype THEN - _timescaledb_internal.time_to_internal(range_end::date) + CASE + WHEN column_type IN ('smallint'::regtype, 'bigint'::regtype, 'integer'::regtype) THEN + CASE WHEN range_end IS NULL + THEN 9223372036854775807 + ELSE _timescaledb_internal.time_to_internal(range_end::bigint) + END + WHEN column_type = 'timestamptz'::regtype THEN + _timescaledb_internal.time_to_internal(range_end::timestamptz) + WHEN column_type = 'timestamp'::regtype THEN + _timescaledb_internal.time_to_internal(range_end::timestamp) + WHEN column_type = 'date'::regtype THEN + _timescaledb_internal.time_to_internal(range_end::date) ELSE NULL END AS range_end - FROM unparsed_missing_slices JOIN _timescaledb_catalog.dimension di USING (column_name); + FROM unparsed_missing_slices; diff --git a/test/sql/updates/post.repair.sql b/test/sql/updates/post.repair.sql new file mode 100644 index 00000000000..d95e294aa60 --- /dev/null +++ b/test/sql/updates/post.repair.sql @@ -0,0 +1,10 @@ +-- 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); + diff --git a/test/sql/updates/setup.check.sql b/test/sql/updates/setup.check.sql new file mode 100644 index 00000000000..2d668b2630c --- /dev/null +++ b/test/sql/updates/setup.check.sql @@ -0,0 +1,22 @@ +-- 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. + +\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); diff --git a/test/sql/updates/setup.repair.sql b/test/sql/updates/setup.repair.sql new file mode 100644 index 00000000000..ce88e504d5c --- /dev/null +++ b/test/sql/updates/setup.repair.sql @@ -0,0 +1,185 @@ +-- 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 < '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_extra(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); + +-- 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. +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_extra', '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_extra 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 remaining that wasn't +-- repaired properly, we will notice them when restoring the +-- contraint. +ALTER TABLE _timescaledb_catalog.chunk_constraint + DROP CONSTRAINT chunk_constraint_dimension_slice_id_fkey; + +CREATE VIEW slices AS ( + SELECT ch.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, + di.id AS dimension_id, + dimension_slice_id, + constraint_name, + attname AS column_name, + column_type, + 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 + JOIN _timescaledb_catalog.dimension di + ON di.hypertable_id = ch.hypertable_id AND attname = di.column_name + ); + +\if :runs_repair_script +-- Break the first time dimension on each table. These are different +-- depending on the time type for the table and we need to check all +-- versions. +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 +); + +-- Delete all dimension slices for one table to break it seriously. It +-- should still be repaired. +DELETE FROM _timescaledb_catalog.dimension_slice WHERE id IN ( + SELECT dimension_slice_id FROM slices + WHERE hypertable = 'repair_test_extra'::regclass +); + +-- 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 no missing partition constraint slices, just one +-- missing partition constraint dimension slice, and several missing +-- partition constraint 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 +); + +\echo **** Expected repairs **** +WITH unparsed_slices AS ( + SELECT dimension_id, + dimension_slice_id, + hypertable, + 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 +) +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); +\endif + +DROP VIEW slices; diff --git a/test/sql/updates/setup.v6-pg11.sql b/test/sql/updates/setup.v6-pg11.sql index 59bf886250d..3dab415cb03 100644 --- a/test/sql/updates/setup.v6-pg11.sql +++ b/test/sql/updates/setup.v6-pg11.sql @@ -6,3 +6,6 @@ \ir setup.continuous_aggs.v2.sql \ir setup.compression.sql \ir setup.policies.sql +\if :TEST_REPAIR +\ir setup.repair.sql +\endif