Fix CAgg on CAgg variable bucket size validation

Previous attempt to fix it (PR #5130) was not entirely correct because
the bucket width calculation for interval width was wrong.

Fixed it by properly calculate the bucket width for intervals using the
Postgres internal function `interval_part` to extract the epoch of the
interval and execute the validations. For integer widths use the already
calculated bucket width.

Fixes #5158, #5168
This commit is contained in:
Fabrízio de Royes Mello 2023-01-10 15:19:18 -03:00
parent ca9d508ede
commit 73df496c75
7 changed files with 1707 additions and 213 deletions

View File

@ -9,14 +9,14 @@ accidentally triggering the load of a previous DB version.**
**Bugfixes**
* #4926 Fix corruption when inserting into compressed chunks
* #5114 Fix issue with deleting data node and dropping database
* #5130 Fix CAgg on CAgg variable bucket size validation
* #5133 Fix CAgg on CAgg using different column order on the original hypertable
* #5152 Fix adding column with NULL constraint to compressed hypertable
* #5170 Fix CAgg on CAgg variable bucket size validation
**Thanks**
* @ikkala for reporting error when adding column with NULL constraint to compressed hypertable
* @salquier-appvizer for reporting error on CAgg on CAgg using different column order on the original hypertable
* @ssmoss for reporting error on CAgg on CAgg variable bucket size validation
* @ssmoss, @adbnexxtlab and @ivanzamanov for reporting error on CAgg on CAgg variable bucket size validation
## 2.9.1 (2022-12-23)

View File

@ -1099,24 +1099,53 @@ cagg_query_supported(const Query *query, StringInfo hint, StringInfo detail, con
static inline int64
get_bucket_width(CAggTimebucketInfo bucket_info)
{
if (bucket_info.bucket_width == BUCKET_WIDTH_VARIABLE)
{
int64 width = 0;
/* calculate the width */
return ts_interval_value_to_internal(IntervalPGetDatum(bucket_info.interval),
bucket_info.bucket_width_type);
switch (bucket_info.bucket_width_type)
{
case INT8OID:
case INT4OID:
case INT2OID:
width = bucket_info.bucket_width;
break;
case INTERVALOID:
{
Datum epoch = DirectFunctionCall2(interval_part,
PointerGetDatum(cstring_to_text("epoch")),
IntervalPGetDatum(bucket_info.interval));
/* cast float8 to int8 */
width = DatumGetInt64(DirectFunctionCall1(dtoi8, epoch));
break;
}
default:
Assert(false);
}
return bucket_info.bucket_width;
return width;
}
static inline Datum
get_bucket_width_datum(CAggTimebucketInfo bucket_info)
{
if (bucket_info.bucket_width == BUCKET_WIDTH_VARIABLE)
return IntervalPGetDatum(bucket_info.interval);
Datum width = (Datum) 0;
return ts_internal_to_interval_value(get_bucket_width(bucket_info),
switch (bucket_info.bucket_width_type)
{
case INT8OID:
case INT4OID:
case INT2OID:
width = ts_internal_to_interval_value(bucket_info.bucket_width,
bucket_info.bucket_width_type);
break;
case INTERVALOID:
width = IntervalPGetDatum(bucket_info.interval);
break;
default:
Assert(false);
}
return width;
}
static CAggTimebucketInfo
@ -1320,8 +1349,8 @@ cagg_validate_query(const Query *query, const bool finalized, const char *cagg_s
/* nested cagg validations */
if (is_nested)
{
int64 bucket_width, bucket_width_parent;
bool is_greater_or_equal_than_parent, is_multiple_of_parent;
int64 bucket_width = 0, bucket_width_parent = 0;
bool is_greater_or_equal_than_parent = true, is_multiple_of_parent = true;
Assert(prev_query->groupClause);
caggtimebucket_validate(&bucket_info_parent,
@ -1354,7 +1383,13 @@ cagg_validate_query(const Query *query, const bool finalized, const char *cagg_s
is_greater_or_equal_than_parent = (bucket_width >= bucket_width_parent);
/* check if buckets are multiple */
if (bucket_width_parent != 0)
{
if (bucket_width_parent > bucket_width && bucket_width != 0)
is_multiple_of_parent = ((bucket_width_parent % bucket_width) == 0);
else
is_multiple_of_parent = ((bucket_width % bucket_width_parent) == 0);
}
/* proceed with validation errors */
if (!is_greater_or_equal_than_parent || !is_multiple_of_parent)
@ -1373,14 +1408,14 @@ cagg_validate_query(const Query *query, const bool finalized, const char *cagg_s
width_parent = get_bucket_width_datum(bucket_info_parent);
width_out_parent = DatumGetCString(OidFunctionCall1(outfuncid, width_parent));
/* new bucket should be greater than the parent */
if (!is_greater_or_equal_than_parent)
message = "greater than";
/* new bucket should be multiple of the parent */
if (!is_multiple_of_parent)
message = "multiple of";
/* new bucket should be greater than the parent */
if (!is_greater_or_equal_than_parent)
message = "greater or equal than";
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot create continuous aggregate with incompatible bucket width"),

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,8 @@
-- Global test variables
\set IS_DISTRIBUTED FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE
-- ########################################################
-- ## INTEGER data type tests
@ -183,20 +184,63 @@ SET timezone TO 'UTC';
\ir include/cagg_on_cagg_validations.sql
--
-- -- Validations using time bucket with timezone (ref issue #5126)
-- Validations using time bucket with timezone (ref issue #5126)
--
\set TIME_DIMENSION_DATATYPE TIMESTAMPTZ
\set IS_TIME_DIMENSION_WITH_TIMEZONE TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE
\set CAGG_NAME_1ST_LEVEL conditions_summary_1_5m
\set CAGG_NAME_2TH_LEVEL conditions_summary_2_1h
\set BUCKET_TZNAME 'US/Pacific'
\set BUCKET_TZNAME_1ST 'US/Pacific'
\set BUCKET_TZNAME_2TH 'US/Pacific'
\set BUCKET_WIDTH_1ST 'INTERVAL \'5 minutes\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 hour\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
\set BUCKET_TZNAME 'US/Pacific'
\set BUCKET_WIDTH_1ST 'INTERVAL \'5 minutes\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'16 minutes\''
\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'
\ir include/cagg_on_cagg_validations.sql
--
-- Variable bucket size with the same timezones
--
\set BUCKET_TZNAME_1ST 'UTC'
\set BUCKET_TZNAME_2TH 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
--
-- Variable bucket size with different timezones
--
\set BUCKET_TZNAME_1ST 'US/Pacific'
\set BUCKET_TZNAME_2TH 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
--
-- TZ bucket on top of non-TZ bucket
--
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE
\set BUCKET_TZNAME_2TH 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
--
-- non-TZ bucket on top of TZ bucket
--
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE
\set BUCKET_TZNAME_1ST 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql

View File

@ -22,7 +22,8 @@ GRANT CREATE ON SCHEMA public TO :ROLE_DEFAULT_PERM_USER;
-- Global test variables
\set IS_DISTRIBUTED TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE
-- ########################################################
-- ## INTEGER data type tests
@ -206,21 +207,64 @@ SET timezone TO 'UTC';
-- Validations using time bucket with timezone (ref issue #5126)
--
\set TIME_DIMENSION_DATATYPE TIMESTAMPTZ
\set IS_TIME_DIMENSION_WITH_TIMEZONE TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE
\set CAGG_NAME_1ST_LEVEL conditions_summary_1_5m
\set CAGG_NAME_2TH_LEVEL conditions_summary_2_1h
\set BUCKET_TZNAME 'US/Pacific'
\set BUCKET_TZNAME_1ST 'US/Pacific'
\set BUCKET_TZNAME_2TH 'US/Pacific'
\set BUCKET_WIDTH_1ST 'INTERVAL \'5 minutes\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 hour\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
\set BUCKET_TZNAME 'US/Pacific'
\set BUCKET_WIDTH_1ST 'INTERVAL \'5 minutes\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'16 minutes\''
\set WARNING_MESSAGE '-- SHOULD ERROR because non-multiple bucket sizes'
\ir include/cagg_on_cagg_validations.sql
--
-- Variable bucket size with the same timezones
--
\set BUCKET_TZNAME_1ST 'UTC'
\set BUCKET_TZNAME_2TH 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
--
-- Variable bucket size with different timezones
--
\set BUCKET_TZNAME_1ST 'US/Pacific'
\set BUCKET_TZNAME_2TH 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
--
-- TZ bucket on top of non-TZ bucket
--
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST FALSE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH TRUE
\set BUCKET_TZNAME_2TH 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
--
-- non-TZ bucket on top of TZ bucket
--
\set IS_TIME_DIMENSION_WITH_TIMEZONE_1ST TRUE
\set IS_TIME_DIMENSION_WITH_TIMEZONE_2TH FALSE
\set BUCKET_TZNAME_1ST 'UTC'
\set BUCKET_WIDTH_1ST 'INTERVAL \'1 day\''
\set BUCKET_WIDTH_2TH 'INTERVAL \'1 month\''
\set WARNING_MESSAGE '-- SHOULD WORK'
\ir include/cagg_on_cagg_validations.sql
-- Cleanup
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER;
DROP DATABASE :DATA_NODE_1;

View File

@ -11,8 +11,8 @@
CREATE MATERIALIZED VIEW :CAGG_NAME_1ST_LEVEL
WITH (timescaledb.continuous) AS
SELECT
\if :IS_TIME_DIMENSION_WITH_TIMEZONE
time_bucket(:BUCKET_WIDTH_1ST, "time", :'BUCKET_TZNAME') AS bucket,
\if :IS_TIME_DIMENSION_WITH_TIMEZONE_1ST
time_bucket(:BUCKET_WIDTH_1ST, "time", :'BUCKET_TZNAME_1ST') AS bucket,
\else
time_bucket(:BUCKET_WIDTH_1ST, "time") AS bucket,
\endif
@ -21,6 +21,8 @@ FROM conditions
GROUP BY 1
WITH NO DATA;
\d+ :CAGG_NAME_1ST_LEVEL
--
-- CAGG on CAGG (2th level)
--
@ -30,8 +32,8 @@ WITH NO DATA;
CREATE MATERIALIZED VIEW :CAGG_NAME_2TH_LEVEL
WITH (timescaledb.continuous) AS
SELECT
\if :IS_TIME_DIMENSION_WITH_TIMEZONE
time_bucket(:BUCKET_WIDTH_2TH, "bucket", :'BUCKET_TZNAME') AS bucket,
\if :IS_TIME_DIMENSION_WITH_TIMEZONE_2TH
time_bucket(:BUCKET_WIDTH_2TH, "bucket", :'BUCKET_TZNAME_2TH') AS bucket,
\else
time_bucket(:BUCKET_WIDTH_2TH, "bucket") AS bucket,
\endif
@ -39,7 +41,10 @@ SELECT
FROM :CAGG_NAME_1ST_LEVEL
GROUP BY 1
WITH NO DATA;
\set ON_ERROR_STOP 0
\d+ :CAGG_NAME_2TH_LEVEL
\set ON_ERROR_STOP 1
\set VERBOSITY terse
--