From 97efca17b2f407bc3d27446ede4e3746f3cb3d2c Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Fri, 5 Jan 2024 21:44:57 +0000 Subject: [PATCH] feat(dw): Add auth token accumulating fact table --- .../oss/postgres/82/01_wh_auth_token.up.sql | 101 ++++ .../db/sqltest/tests/domain/wh_user_id.sql | 37 ++ .../tests/wh/auth_token/constraints.sql | 507 ++++++++++++++++++ 3 files changed, 645 insertions(+) create mode 100644 internal/db/schema/migrations/oss/postgres/82/01_wh_auth_token.up.sql create mode 100644 internal/db/sqltest/tests/domain/wh_user_id.sql create mode 100644 internal/db/sqltest/tests/wh/auth_token/constraints.sql diff --git a/internal/db/schema/migrations/oss/postgres/82/01_wh_auth_token.up.sql b/internal/db/schema/migrations/oss/postgres/82/01_wh_auth_token.up.sql new file mode 100644 index 0000000000..bdd2a2a14b --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/82/01_wh_auth_token.up.sql @@ -0,0 +1,101 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + + -- wh_user_id is similar to wt_user_id but for the data warehouse. + -- Unlike wt_user_id, this allows for nulls. + create domain wh_user_id as text + check( + length(trim(value)) > 10 or value in ('u_anon', 'u_auth', 'u_recovery') + ); + comment on domain wh_user_id is + '"u_anon", "u_auth", "u_recovery" or random ID generated with github.com/hashicorp/go-secure-stdlib/base62'; + + create table wh_auth_token_accumulating_fact ( + auth_token_id wt_public_id primary key, + user_id wh_user_id not null, + user_key wh_dim_key not null + references wh_user_dimension (key) + on delete restrict + on update cascade, + + -- date and time foreign keys + auth_token_issued_date_key integer not null + references wh_date_dimension (key) + on delete restrict + on update cascade, + auth_token_issued_time_key integer not null + references wh_time_of_day_dimension (key) + on delete restrict + on update cascade, + auth_token_issued_time wh_timestamp, + + auth_token_deleted_date_key integer default -1 not null + references wh_date_dimension (key) + on delete restrict + on update cascade, + auth_token_deleted_time_key integer default -1 not null + references wh_time_of_day_dimension (key) + on delete restrict + on update cascade, + auth_token_deleted_time wh_timestamp default 'infinity'::timestamptz, + + auth_token_approximate_last_access_date_key integer default -1 not null + references wh_date_dimension (key) + on delete restrict + on update cascade, + auth_token_approximate_last_access_time_key integer default -1 not null + references wh_time_of_day_dimension (key) + on delete restrict + on update cascade, + auth_token_approximate_last_access_time wh_timestamp, + + auth_token_approximate_active_time_range tstzrange not null default tstzrange(current_timestamp, current_timestamp, '[]'), + auth_token_valid_time_range tstzrange not null default tstzrange(current_timestamp, 'infinity'::timestamptz, '[]'), + + -- the auth_token_count must always be 1 + -- this is a common pattern in data warehouse models + -- See The Data Warehouse Toolkit, Third Edition + -- by Ralph Kimball and Margy Ross for more information + auth_token_count smallint default 1 not null + constraint auth_token_count_must_be_1 + check(auth_token_count = 1), + + constraint last_accessed_time_lte_deleted_time + check(auth_token_approximate_last_access_time <= auth_token_deleted_time), + + constraint active_time_lower_eq_issued_time + check(lower(auth_token_approximate_active_time_range) = auth_token_issued_time), + constraint active_time_upper_eq_last_accessed_time + check(upper(auth_token_approximate_active_time_range) = auth_token_approximate_last_access_time), + constraint valid_time_lower_eq_issued + check(lower(auth_token_valid_time_range) = auth_token_issued_time), + constraint valid_time_upper_eq_deleted + check(upper(auth_token_valid_time_range) = auth_token_deleted_time), + constraint active_time_contained_by_valid_time + check(auth_token_approximate_active_time_range <@ auth_token_valid_time_range) + ); + comment on table wh_auth_token_accumulating_fact is + 'The Wh Auth Token Accumulating Fact table is an accumulating fact table. ' + 'The grain of the fact table is one row per auth token.'; + + create function wh_insert_auth_token() returns trigger + as $$ + begin + select user_id + into new.user_id + from wh_user_dimension + where key = new.user_key; + + return new; + end; + $$ language plpgsql; + + create trigger wh_insert_auth_token before insert on wh_auth_token_accumulating_fact + for each row execute function wh_insert_auth_token(); + + create trigger immutable_columns before update on wh_auth_token_accumulating_fact + for each row execute procedure immutable_columns('user_id', 'user_key', 'auth_token_issued_time', 'auth_token_issued_time_key', 'auth_token_issued_date_key'); + +commit; diff --git a/internal/db/sqltest/tests/domain/wh_user_id.sql b/internal/db/sqltest/tests/domain/wh_user_id.sql new file mode 100644 index 0000000000..144c918544 --- /dev/null +++ b/internal/db/sqltest/tests/domain/wh_user_id.sql @@ -0,0 +1,37 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + select plan(3); + + create table test_wh_user_id ( + id wh_user_id + ); + + prepare too_short as + insert into test_wh_user_id (id) + values ('short'); + select throws_ok( + 'too_short', + '23514', + 'value for domain wh_user_id violates check constraint "wh_user_id_check"' + ); + prepare too_short_trim as + insert into test_wh_user_id (id) + values (' short '); + select throws_ok( + 'too_short_trim', + '23514', + 'value for domain wh_user_id violates check constraint "wh_user_id_check"' + ); + + prepare valid as + insert into test_wh_user_id (id) + values ('u_123456789'), + ('u_anon'), + ('u_auth'), + ('u_recovery'); + select lives_ok('valid'); + + select * from finish(); +rollback; diff --git a/internal/db/sqltest/tests/wh/auth_token/constraints.sql b/internal/db/sqltest/tests/wh/auth_token/constraints.sql new file mode 100644 index 0000000000..b61e366cd4 --- /dev/null +++ b/internal/db/sqltest/tests/wh/auth_token/constraints.sql @@ -0,0 +1,507 @@ +-- Copyright (c) HashiCorp, Inc. +-- SPDX-License-Identifier: BUSL-1.1 + +begin; + select plan(13); + + -- Create a user dimension for corresponding auth token's user. + insert into wh_user_dimension ( + user_id, user_name, user_description, + auth_account_id, auth_account_type, auth_account_name, auth_account_description, + auth_method_id, auth_method_type, auth_method_name, auth_method_description, + user_organization_id, user_organization_name, user_organization_description, + current_row_indicator, + row_effective_time, row_expiration_time, + auth_method_external_id, auth_account_external_id, auth_account_full_name, auth_account_email + ) values ( + 'u_____user1', 'None', 'None', + 'a______acc1', 'None', 'None', 'None', + 'am______am1', 'None', 'None', 'None', + 'o______org1', 'None', 'None', + 'current', + now(), 'infinity'::timestamptz, + 'None', 'None', 'None', 'None' + ); + + + prepare insert_access_time_after_delete as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 11:00:00'::timestamptz), + wh_time_key('2023-12-13 11:00:00'::timestamptz), + '2023-12-13 11:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token1', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts, accessed_timestamp.ts), + tstzrange(issued_timestamp.ts, accessed_timestamp.ts), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select throws_ok( + 'insert_access_time_after_delete', + '23514', + 'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "last_accessed_time_lte_deleted_time"' + ); + + prepare insert_active_time_after_issued as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 13:00:00'::timestamptz), + wh_time_key('2023-12-13 13:00:00'::timestamptz), + '2023-12-13 13:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token2', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts + interval '10 minutes', accessed_timestamp.ts), + tstzrange(issued_timestamp.ts, deleted_timestamp.ts), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select throws_ok( + 'insert_active_time_after_issued', + '23514', + 'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "active_time_lower_eq_issued_time"' + ); + + prepare insert_active_time_before_access as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 13:00:00'::timestamptz), + wh_time_key('2023-12-13 13:00:00'::timestamptz), + '2023-12-13 13:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token2', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts, accessed_timestamp.ts - interval '10 minutes'), + tstzrange(issued_timestamp.ts, deleted_timestamp.ts), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select throws_ok( + 'insert_active_time_before_access', + '23514', + 'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "active_time_upper_eq_last_accessed_time"' + ); + + prepare insert_active_not_contained_by_valid as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 11:00:00'::timestamptz), + wh_time_key('2023-12-13 11:00:00'::timestamptz), + '2023-12-13 11:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token1', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts, accessed_timestamp.ts), + tstzrange(issued_timestamp.ts, deleted_timestamp.ts), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select throws_ok( + 'insert_active_not_contained_by_valid', + '23514', + 'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "active_time_contained_by_valid_time"' + ); + + prepare insert_valid_time_before_issued as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 13:00:00'::timestamptz), + wh_time_key('2023-12-13 13:00:00'::timestamptz), + '2023-12-13 13:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token2', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts, accessed_timestamp.ts), + tstzrange(issued_timestamp.ts - interval '10 minutes', deleted_timestamp.ts), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select throws_ok( + 'insert_valid_time_before_issued', + '23514', + 'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "valid_time_lower_eq_issued"' + ); + + prepare insert_valid_time_before_deleted as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 13:00:00'::timestamptz), + wh_time_key('2023-12-13 13:00:00'::timestamptz), + '2023-12-13 13:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token2', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts, accessed_timestamp.ts), + tstzrange(issued_timestamp.ts, deleted_timestamp.ts - interval '10 minutes'), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select throws_ok( + 'insert_valid_time_before_deleted', + '23514', + 'new row for relation "wh_auth_token_accumulating_fact" violates check constraint "valid_time_upper_eq_deleted"' + ); + + prepare insert_success as + with + issued_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 10:00:00'::timestamptz), + wh_time_key('2023-12-13 10:00:00'::timestamptz), + '2023-12-13 10:00:00'::timestamptz + ), + accessed_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 12:00:00'::timestamptz), + wh_time_key('2023-12-13 12:00:00'::timestamptz), + '2023-12-13 12:00:00'::timestamptz + ), + deleted_timestamp(date_dim_key, time_dim_key, ts) as ( + select wh_date_key('2023-12-13 13:00:00'::timestamptz), + wh_time_key('2023-12-13 13:00:00'::timestamptz), + '2023-12-13 13:00:00'::timestamptz + ), + user_dim(key) as ( + select key + from wh_user_dimension + where user_id = 'u_____user1' + ) + insert into wh_auth_token_accumulating_fact ( + auth_token_id, + user_key, + auth_token_issued_date_key, + auth_token_issued_time_key, + auth_token_issued_time, + auth_token_deleted_date_key, + auth_token_deleted_time_key, + auth_token_deleted_time, + auth_token_approximate_last_access_date_key, + auth_token_approximate_last_access_time_key, + auth_token_approximate_last_access_time, + auth_token_approximate_active_time_range, + auth_token_valid_time_range, + auth_token_count + ) + select 'tok___token2', + user_dim.key, + issued_timestamp.date_dim_key, + issued_timestamp.time_dim_key, + issued_timestamp.ts, + deleted_timestamp.date_dim_key, + deleted_timestamp.time_dim_key, + deleted_timestamp.ts, + accessed_timestamp.date_dim_key, + accessed_timestamp.time_dim_key, + accessed_timestamp.ts, + tstzrange(issued_timestamp.ts, accessed_timestamp.ts), + tstzrange(issued_timestamp.ts, deleted_timestamp.ts), + 1 + from user_dim, + issued_timestamp, + deleted_timestamp, + accessed_timestamp; + select lives_ok('insert_success'); + -- ensure user_id was properly set + select is(wh_auth_token_accumulating_fact.user_id, 'u_____user1') + from wh_auth_token_accumulating_fact + where auth_token_id = 'tok___token2'; + + prepare update_user_id as + update wh_auth_token_accumulating_fact set user_id = 'u_____user2' + where auth_token_id = 'tok___token2'; + select throws_ok( + 'update_user_id', + '23601', + 'immutable column: wh_auth_token_accumulating_fact.user_id' + ); + + prepare update_user_key as + update wh_auth_token_accumulating_fact set user_key = 'key_2' + where auth_token_id = 'tok___token2'; + select throws_ok( + 'update_user_key', + '23601', + 'immutable column: wh_auth_token_accumulating_fact.user_key' + ); + + prepare update_auth_token_issued_time as + update wh_auth_token_accumulating_fact set auth_token_issued_time = '2023-12-13 11:01:00'::timestamptz + where auth_token_id = 'tok___token2'; + select throws_ok( + 'update_auth_token_issued_time', + '23601', + 'immutable column: wh_auth_token_accumulating_fact.auth_token_issued_time' + ); + + prepare update_auth_token_issued_time_key as + update wh_auth_token_accumulating_fact set auth_token_issued_time_key = wh_time_key('2023-12-13 11:01:00'::timestamptz) + where auth_token_id = 'tok___token2'; + select throws_ok( + 'update_auth_token_issued_time_key', + '23601', + 'immutable column: wh_auth_token_accumulating_fact.auth_token_issued_time_key' + ); + + prepare update_auth_token_issued_date_key as + update wh_auth_token_accumulating_fact set auth_token_issued_date_key = wh_date_key('2023-12-14 11:00:00'::timestamptz) + where auth_token_id = 'tok___token2'; + select throws_ok( + 'update_auth_token_issued_date_key', + '23601', + 'immutable column: wh_auth_token_accumulating_fact.auth_token_issued_date_key' + ); + + select * from finish(); +rollback;