From c71c3d044bbbfd5eb1d405124baf5c7780607cc0 Mon Sep 17 00:00:00 2001 From: zhubin79 <18784715772@163.com> Date: Thu, 1 Aug 2024 18:40:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=88=97numeric=E3=80=81floa?= =?UTF-8?q?t=E6=95=B0=E6=8D=AE=E7=B1=BB=E5=9E=8B=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../optimizer/commands/tablecmds.cpp | 82 +++++++++++++++++++ src/include/commands/tablecmds.h | 10 +++ src/include/utils/numeric.h | 1 + src/test/regress/expected/test_float.out | 57 +++++++++++++ .../expected/test_numeric_with_neg_scale.out | 20 +++++ src/test/regress/sql/test_float.sql | 20 ++++- .../sql/test_numeric_with_neg_scale.sql | 9 ++ 7 files changed, 198 insertions(+), 1 deletion(-) diff --git a/src/gausskernel/optimizer/commands/tablecmds.cpp b/src/gausskernel/optimizer/commands/tablecmds.cpp index 3f4606f6f..f80a412c8 100755 --- a/src/gausskernel/optimizer/commands/tablecmds.cpp +++ b/src/gausskernel/optimizer/commands/tablecmds.cpp @@ -10123,6 +10123,16 @@ static void ATRewriteTableInternal(AlteredTableInfo* tab, Relation oldrel, Relat } else { ((HeapScanDesc) scan)->rs_tupdesc = oldTupDesc; while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { + if (tab->check_pass_with_relempty == AT_FASN_FAIL_PRECISION) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column to be modified must be empty to decrease precision or scale"))); + } else if (tab->check_pass_with_relempty == AT_FASN_FAIL_TYPE) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("column to be modified must be empty to change datatype"))); + } + if (tab->rewrite > 0) { Oid tupOid = InvalidOid; int newvals_num = 0; @@ -16160,6 +16170,76 @@ void CheckHugeToast(AlteredTableInfo *tab, Relation rel, AttrNumber attnum) } } +/** + * Check numeric type modify. + * column to be modified must be empty to decrease precision or scale; + * column to be modified must be empty to change datatype. + * + * Only valid numeric scale decrease, And when set behavior_compat_options = 'float_as_numeric' + * (float use numeric indicate), valid float(p) can't decrease precision and can't change to other datatype. +*/ +static int CheckFloatIllegalTypeConversion(Relation rel, Oid origintype, int32 origintypmod, Oid targettype, int32 targettypmod) +{ + int32 oriprecision; + int16 oriscale; + int32 tarprecision; + int16 tarscale; + bool compatibilityversion; + int result = AT_FASN_PASS; + + if (RelationIsCUFormat(rel) || !(u_sess->attr.attr_sql.sql_compatibility == A_FORMAT)) { + return result; + } + + compatibilityversion = FLOAT_AS_NUMERIC && t_thrd.proc->workingVersionNum >= FLOAT_VERSION_NUMBER; + + if (origintype == NUMERICOID) { + oriprecision = (int32) ((((uint32)(origintypmod - VARHDRSZ)) >> 16) & 0xffff); + oriscale = (int16) (((uint32)(origintypmod - VARHDRSZ)) & 0xffff); + } + + if (targettype == NUMERICOID) { + tarprecision = (int32) ((((uint32)(targettypmod - VARHDRSZ)) >> 16) & 0xffff); + tarscale = (int16) (((uint32)(targettypmod - VARHDRSZ)) & 0xffff); + + if (origintype == NUMERICOID) { + if (compatibilityversion) { + // float decrease precision + if (IS_FLOAT_AS_NUMERIC(oriscale) && IS_FLOAT_AS_NUMERIC(tarscale) && tarprecision < oriprecision) { + result = AT_FASN_FAIL_PRECISION; + } + // can't change datatype between float and numeric + else if (IS_FLOAT_AS_NUMERIC(oriscale) != IS_FLOAT_AS_NUMERIC(tarscale)) { + result = AT_FASN_FAIL_PRECISION; + } + } + + // numeric decrease scale + if (!IS_FLOAT_AS_NUMERIC(oriscale) && !IS_FLOAT_AS_NUMERIC(tarscale) && tarscale < oriscale) { + result = AT_FASN_FAIL_PRECISION; + } + + return result; + } else if (compatibilityversion && IS_FLOAT_AS_NUMERIC(tarscale) + && (origintype == INT4OID || origintype == INT2OID || origintype ==INT1OID)) { + // O* has no int type, so all use NUMBER(38) instead. + if (ceil(log10(2) * tarprecision) < 38) { + result = AT_FASN_FAIL_PRECISION; + + } + // convert int to numeric + return result; + } + } + + if (compatibilityversion && origintype == NUMERICOID && IS_FLOAT_AS_NUMERIC(oriscale) && targettype != origintype) { + // can't change other float to other datatype. + result = AT_FASN_FAIL_TYPE; + } + + return result; +} + /* * ALTER COLUMN TYPE */ @@ -16313,6 +16393,8 @@ static void ATPrepAlterColumnType(List** wqueue, AlteredTableInfo* tab, Relation transform = (Node*)makeVar(1, attnum, attTup->atttypid, attTup->atttypmod, attTup->attcollation, 0); } + tab->check_pass_with_relempty = CheckFloatIllegalTypeConversion(rel, attTup->atttypid, attTup->atttypmod, targettype, targettypmod); + transform = coerce_to_target_type(pstate, transform, exprType(transform), diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index fdcccfea7..10aa1e025 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -65,6 +65,15 @@ #define AT_NUM_PASSES 10 #endif +/** + * state information of alter float datatype result (at float_as_numeric). + * + * Phase 3 scan if table not empty and 'check_floatasnumeric' > 0, go err. + */ +#define AT_FASN_PASS 0 +#define AT_FASN_FAIL_PRECISION 1 +#define AT_FASN_FAIL_TYPE 2 + typedef struct AlteredTableInfo { /* Information saved before any work commences: */ Oid relid; /* Relation to work on */ @@ -78,6 +87,7 @@ typedef struct AlteredTableInfo { List* newvals; /* List of NewColumnValue */ bool new_notnull; /* T if we added new NOT NULL constraints */ int rewrite; /* Reason if a rewrite is forced */ + int check_pass_with_relempty; /* alter column check condition, require table empty condition in phase 3 */ Oid newTableSpace; /* new tablespace; 0 means no change */ /* Objects to rebuild after completing ALTER TYPE operations */ List* changedConstraintOids; /* OIDs of constraints to rebuild */ diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h index c7c33cb0b..3c82cec89 100644 --- a/src/include/utils/numeric.h +++ b/src/include/utils/numeric.h @@ -156,6 +156,7 @@ typedef struct NumericData* Numeric; #define NUMERIC_MAX_PRECISION 1000 #define NUMERIC_MAX_SCALE 1000 #define NUMERIC_MIN_SCALE -84 +#define IS_FLOAT_AS_NUMERIC(scale) ((scale) == INT16_MIN) /* * Internal limits on the scales chosen for calculation results diff --git a/src/test/regress/expected/test_float.out b/src/test/regress/expected/test_float.out index 99aa7c424..edea5c465 100644 --- a/src/test/regress/expected/test_float.out +++ b/src/test/regress/expected/test_float.out @@ -123,6 +123,63 @@ SELECT * FROM t1; NaN | NaN | NaN (2 rows) +DROP TABLE t1; +-- alter table test +set behavior_compat_options = 'float_as_numeric, truncate_numeric_tail_zero'; +CREATE TABLE t1(col1 int, col2 float(5), col3 float(44), col4 float(20)); +INSERT INTO t1 VALUES (1, 12345678901234.567890123456789, 123456789123456789123456789123456789123456789.123456789123456789123456789123456789, 123.123); +INSERT INTO t1 VALUES (12, 0.12345678901234567890123456789, 0.123456789123456789123456789123456789123456789123456789123456789123456789123456789,123.123); +INSERT INTO t1 VALUES (21, 123456789.12345,123456789123456789123456789.12345678912, 123.123); +SELECT pg_get_tabledef('t1'); + pg_get_tabledef +----------------------------------------- + SET search_path = test_float; + + CREATE TABLE t1 ( + + col1 integer, + + col2 float(5), + + col3 float(44), + + col4 float(20) + + ) + + WITH (orientation=row, compression=no); +(1 row) + +SELECT * FROM t1 ORDER BY col1; + col1 | col2 | col3 | col4 +------+----------------+-----------------------------------------------+--------- + 1 | 12000000000000 | 123456789123460000000000000000000000000000000 | 123.123 + 12 | .12 | .12345678912346 | 123.123 + 21 | 120000000 | 123456789123460000000000000 | 123.123 +(3 rows) + +-- alter table not empty +ALTER TABLE t1 MODIFY (col1 float(1)); -- error +ERROR: column to be modified must be empty to decrease precision or scale +-- alter table empty +DELETE FROM t1; +SELECT * FROM t1 ORDER BY col1; + col1 | col2 | col3 | col4 +------+------+------+------ +(0 rows) + +ALTER TABLE t1 MODIFY (col1 float(1)); -- success +SELECT pg_get_tabledef('t1'); + pg_get_tabledef +----------------------------------------- + SET search_path = test_float; + + CREATE TABLE t1 ( + + col1 float(1), + + col2 float(5), + + col3 float(44), + + col4 float(20) + + ) + + WITH (orientation=row, compression=no); +(1 row) + +SELECT * FROM t1 ORDER BY col1; + col1 | col2 | col3 | col4 +------+------+------+------ +(0 rows) + DROP TABLE t1; -- PL/SQL test CREATE OR REPLACE PACKAGE pak1 as diff --git a/src/test/regress/expected/test_numeric_with_neg_scale.out b/src/test/regress/expected/test_numeric_with_neg_scale.out index 3b795b769..73f7125b1 100644 --- a/src/test/regress/expected/test_numeric_with_neg_scale.out +++ b/src/test/regress/expected/test_numeric_with_neg_scale.out @@ -187,6 +187,26 @@ Table "numeric_negative_scale_test.t2" DROP TABLE t1; DROP TABLE t2; +-- test alter table to decrease scale +CREATE TABLE t1 (c1 int, c2 numeric(5, 2), c3 numeric(5, -2)); +INSERT INTO t1 VALUES (1, 546.12, 456135.12); +SELECT * FROM t1; + c1 | c2 | c3 +----+--------+-------- + 1 | 546.12 | 456100 +(1 row) + +ALTER TABLE t1 MODIFY (c2 numeric(5, 1)); -- error +ERROR: column to be modified must be empty to decrease precision or scale +ALTER TABLE t1 MODIFY (c3 numeric(5, -3)); -- error +ERROR: column to be modified must be empty to decrease precision or scale +SELECT * FROM t1; + c1 | c2 | c3 +----+--------+-------- + 1 | 546.12 | 456100 +(1 row) + +DROP TABLE t1; CREATE TABLE t3(a numeric(1,1001)); ERROR: NUMERIC scale must be between -84 and 1000 LINE 1: CREATE TABLE t3(a numeric(1,1001)); diff --git a/src/test/regress/sql/test_float.sql b/src/test/regress/sql/test_float.sql index 7d43409ce..80cca4133 100644 --- a/src/test/regress/sql/test_float.sql +++ b/src/test/regress/sql/test_float.sql @@ -56,6 +56,24 @@ INSERT INTO t1 SELECT 'NaN', 'NaN', 'NaN'; SELECT * FROM t1; DROP TABLE t1; +-- alter table test +set behavior_compat_options = 'float_as_numeric, truncate_numeric_tail_zero'; +CREATE TABLE t1(col1 int, col2 float(5), col3 float(44), col4 float(20)); +INSERT INTO t1 VALUES (1, 12345678901234.567890123456789, 123456789123456789123456789123456789123456789.123456789123456789123456789123456789, 123.123); +INSERT INTO t1 VALUES (12, 0.12345678901234567890123456789, 0.123456789123456789123456789123456789123456789123456789123456789123456789123456789,123.123); +INSERT INTO t1 VALUES (21, 123456789.12345,123456789123456789123456789.12345678912, 123.123); +SELECT pg_get_tabledef('t1'); +SELECT * FROM t1 ORDER BY col1; +-- alter table not empty +ALTER TABLE t1 MODIFY (col1 float(1)); -- error +-- alter table empty +DELETE FROM t1; +SELECT * FROM t1 ORDER BY col1; +ALTER TABLE t1 MODIFY (col1 float(1)); -- success +SELECT pg_get_tabledef('t1'); +SELECT * FROM t1 ORDER BY col1; +DROP TABLE t1; + -- PL/SQL test CREATE OR REPLACE PACKAGE pak1 as @@ -83,4 +101,4 @@ DROP PACKAGE pak1; reset behavior_compat_options; reset current_schema; -drop schema test_float cascade; \ No newline at end of file +drop schema test_float cascade; diff --git a/src/test/regress/sql/test_numeric_with_neg_scale.sql b/src/test/regress/sql/test_numeric_with_neg_scale.sql index 7b57dae6f..aeb9245e0 100644 --- a/src/test/regress/sql/test_numeric_with_neg_scale.sql +++ b/src/test/regress/sql/test_numeric_with_neg_scale.sql @@ -71,6 +71,15 @@ CREATE TABLE t2(a numeric(1,-84)); DROP TABLE t1; DROP TABLE t2; +-- test alter table to decrease scale +CREATE TABLE t1 (c1 int, c2 numeric(5, 2), c3 numeric(5, -2)); +INSERT INTO t1 VALUES (1, 546.12, 456135.12); +SELECT * FROM t1; +ALTER TABLE t1 MODIFY (c2 numeric(5, 1)); -- error +ALTER TABLE t1 MODIFY (c3 numeric(5, -3)); -- error +SELECT * FROM t1; +DROP TABLE t1; + CREATE TABLE t3(a numeric(1,1001)); CREATE TABLE t3(a numeric(1,-85)); CREATE TABLE t3(a numeric(1,1001));