Disallow distributing by numeric with negative scale

PG15 allows numeric scale to be negative or greater than precision. This
causes issues and we may end up routing queries to a wrong shard due to
differing hash results after rounding.

Formerly, when specifying NUMERIC(precision, scale), the scale had to be
in the range [0, precision], which was per SQL spec. PG15 extends the
range of allowed scales to [-1000, 1000].

A negative scale implies rounding before the decimal point. For
example, a column might be declared with a scale of -3 to round values
to the nearest thousand. Note that the display scale remains
non-negative, so in this case the display scale will be zero, and all
digits before the decimal point will be displayed.

Relevant PG commit: 085f931f52494e1f304e35571924efa6fcdc2b44
pull/6297/head
Hanefi Onaldi 2022-09-05 16:05:46 +03:00
parent d7f41cacbe
commit 85b19c851a
No known key found for this signature in database
GPG Key ID: F18CDB10BA0DFDC7
3 changed files with 115 additions and 1 deletions

View File

@ -139,6 +139,14 @@ static Oid DropFKeysAndUndistributeTable(Oid relationId);
static void DropFKeysRelationInvolvedWithTableType(Oid relationId, int tableTypeFlag);
static void CopyLocalDataIntoShards(Oid relationId);
static List * TupleDescColumnNameList(TupleDesc tupleDescriptor);
#if (PG_VERSION_NUM >= PG_VERSION_15)
static bool DistributionColumnUsesNumericColumnNegativeScale(TupleDesc relationDesc,
Var *distributionColumn);
static int numeric_typmod_scale(int32 typmod);
static bool is_valid_numeric_typmod(int32 typmod);
#endif
static bool DistributionColumnUsesGeneratedStoredColumn(TupleDesc relationDesc,
Var *distributionColumn);
static bool CanUseExclusiveConnections(Oid relationId, bool localTableEmpty);
@ -1681,6 +1689,20 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn,
"AS (...) STORED.")));
}
#if (PG_VERSION_NUM >= PG_VERSION_15)
/* verify target relation is not distributed by a column of type numeric with negative scale */
if (distributionMethod != DISTRIBUTE_BY_NONE &&
DistributionColumnUsesNumericColumnNegativeScale(relationDesc,
distributionColumn))
{
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot distribute relation: %s", relationName),
errdetail("Distribution column must not use numeric type "
"with negative scale")));
}
#endif
/* check for support function needed by specified partition method */
if (distributionMethod == DISTRIBUTE_BY_HASH)
{
@ -2401,6 +2423,59 @@ RelationUsesIdentityColumns(TupleDesc relationDesc)
}
#if (PG_VERSION_NUM >= PG_VERSION_15)
/*
* is_valid_numeric_typmod checks if the typmod value is valid
*
* Because of the offset, valid numeric typmods are at least VARHDRSZ
*
* Copied from PG. See numeric.c for understanding how this works.
*/
static bool
is_valid_numeric_typmod(int32 typmod)
{
return typmod >= (int32) VARHDRSZ;
}
/*
* numeric_typmod_scale extracts the scale from a numeric typmod.
*
* Copied from PG. See numeric.c for understanding how this works.
*
*/
static int
numeric_typmod_scale(int32 typmod)
{
return (((typmod - VARHDRSZ) & 0x7ff) ^ 1024) - 1024;
}
/*
* DistributionColumnUsesNumericColumnNegativeScale returns whether a given relation uses
* numeric data type with negative scale on distribution column
*/
static bool
DistributionColumnUsesNumericColumnNegativeScale(TupleDesc relationDesc,
Var *distributionColumn)
{
Form_pg_attribute attributeForm = TupleDescAttr(relationDesc,
distributionColumn->varattno - 1);
if (attributeForm->atttypid == NUMERICOID &&
is_valid_numeric_typmod(attributeForm->atttypmod) &&
numeric_typmod_scale(attributeForm->atttypmod) < 0)
{
return true;
}
return false;
}
#endif
/*
* DistributionColumnUsesGeneratedStoredColumn returns whether a given relation uses
* GENERATED ALWAYS AS (...) STORED on distribution column

View File

@ -392,9 +392,37 @@ ON (true)
WHEN MATCHED THEN
UPDATE SET x = (SELECT count(*) FROM tbl2);
ERROR: MERGE command is not supported on Citus tables yet
-- test numeric types with negative scale
CREATE TABLE numeric_negative_scale(numeric_column numeric(3,-1), orig_value int);
INSERT into numeric_negative_scale SELECT x,x FROM generate_series(111, 115) x;
-- verify that we can not distribute by a column that has numeric type with negative scale
SELECT create_distributed_table('numeric_negative_scale','numeric_column');
ERROR: cannot distribute relation: numeric_negative_scale
DETAIL: Distribution column must not use numeric type with negative scale
-- However, we can distribute by other columns
SELECT create_distributed_table('numeric_negative_scale','orig_value');
NOTICE: Copying data from local table...
NOTICE: copying the data has completed
DETAIL: The local data in the table is no longer visible, but is still on disk.
HINT: To remove the local data, run: SELECT truncate_local_data_after_distributing_table($$pg15.numeric_negative_scale$$)
create_distributed_table
---------------------------------------------------------------------
(1 row)
SELECT * FROM numeric_negative_scale ORDER BY 1,2;
numeric_column | orig_value
---------------------------------------------------------------------
110 | 111
110 | 112
110 | 113
110 | 114
120 | 115
(5 rows)
-- Clean up
DROP SCHEMA pg15 CASCADE;
NOTICE: drop cascades to 9 other objects
NOTICE: drop cascades to 10 other objects
DETAIL: drop cascades to collation german_phonebook_test
drop cascades to collation default_provider
drop cascades to table sale
@ -404,3 +432,4 @@ drop cascades to view sale_triggers
drop cascades to table generated_stored_ref
drop cascades to table tbl1
drop cascades to table tbl2
drop cascades to table numeric_negative_scale

View File

@ -245,5 +245,15 @@ ON (true)
WHEN MATCHED THEN
UPDATE SET x = (SELECT count(*) FROM tbl2);
-- test numeric types with negative scale
CREATE TABLE numeric_negative_scale(numeric_column numeric(3,-1), orig_value int);
INSERT into numeric_negative_scale SELECT x,x FROM generate_series(111, 115) x;
-- verify that we can not distribute by a column that has numeric type with negative scale
SELECT create_distributed_table('numeric_negative_scale','numeric_column');
-- However, we can distribute by other columns
SELECT create_distributed_table('numeric_negative_scale','orig_value');
SELECT * FROM numeric_negative_scale ORDER BY 1,2;
-- Clean up
DROP SCHEMA pg15 CASCADE;