From 03337cf5da6f02d6d7cfc4d70ee381e3e6af9394 Mon Sep 17 00:00:00 2001 From: Midhush Karthic Date: Sun, 27 Sep 2026 17:36:36 -0400 Subject: [PATCH v1] Add target column context to errors --- src/backend/catalog/heap.c | 11 +- src/backend/catalog/pg_attrdef.c | 4 + src/backend/commands/tablecmds.c | 10 ++ src/backend/commands/typecmds.c | 4 +- src/backend/executor/execExpr.c | 2 + src/backend/executor/execExprInterp.c | 60 +++++++ src/backend/jit/llvm/llvmjit_expr.c | 6 + src/backend/jit/llvm/llvmjit_types.c | 1 + src/backend/optimizer/util/clauses.c | 27 ++- src/backend/parser/parse_coerce.c | 72 ++++++++ src/backend/parser/parse_target.c | 6 +- src/backend/rewrite/rewriteHandler.c | 4 + src/include/catalog/catversion.h | 2 +- src/include/catalog/heap.h | 3 +- src/include/executor/execExpr.h | 3 + src/include/nodes/primnodes.h | 5 + src/include/parser/parse_coerce.h | 2 + src/test/regress/expected/arrays.out | 1 + src/test/regress/expected/bit.out | 3 + src/test/regress/expected/char.out | 2 + src/test/regress/expected/char_1.out | 2 + src/test/regress/expected/char_2.out | 2 + src/test/regress/expected/copy.out | 2 + src/test/regress/expected/domain.out | 5 + src/test/regress/expected/numeric.out | 10 ++ src/test/regress/expected/varchar.out | 221 ++++++++++++++++++++++++ src/test/regress/expected/varchar_1.out | 221 ++++++++++++++++++++++++ src/test/regress/expected/varchar_2.out | 221 ++++++++++++++++++++++++ src/test/regress/sql/varchar.sql | 190 ++++++++++++++++++++ 29 files changed, 1091 insertions(+), 11 deletions(-) diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 1c188b7a0ff..a83691a5c6e 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -2459,7 +2459,8 @@ AddRelationNewConstraints(Relation rel, expr = cookDefault(pstate, colDef->raw_default, atp->atttypid, atp->atttypmod, NameStr(atp->attname), - atp->attgenerated); + atp->attgenerated, + RelationGetRelid(rel), colDef->attnum); /* * If the expression is just a NULL constant, we do not bother to make @@ -3355,6 +3356,7 @@ check_virtual_generated_security(ParseState *pstate, Node *node) * If atttypid is not InvalidOid, coerce the expression to the specified * type (and typmod atttypmod). attname is only needed in this case: * it is used in the error message, if any. + * relid and attnum identify the destination column for coercion errors. */ Node * cookDefault(ParseState *pstate, @@ -3362,7 +3364,8 @@ cookDefault(ParseState *pstate, Oid atttypid, int32 atttypmod, const char *attname, - char attgenerated) + char attgenerated, + Oid relid, AttrNumber attnum) { Node *expr; @@ -3405,6 +3408,7 @@ cookDefault(ParseState *pstate, if (OidIsValid(atttypid)) { Oid type_id = exprType(expr); + Node *orig_expr = expr; expr = coerce_to_target_type(pstate, expr, type_id, atttypid, atttypmod, @@ -3420,6 +3424,9 @@ cookDefault(ParseState *pstate, format_type_be(atttypid), format_type_be(type_id)), errhint("You will need to rewrite or cast the expression."))); + + if (OidIsValid(relid)) + set_coercion_target(expr, orig_expr, relid, attnum); } /* diff --git a/src/backend/catalog/pg_attrdef.c b/src/backend/catalog/pg_attrdef.c index 60bd7a40759..5abb2f48e87 100644 --- a/src/backend/catalog/pg_attrdef.c +++ b/src/backend/catalog/pg_attrdef.c @@ -22,6 +22,7 @@ #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_attrdef.h" +#include "parser/parse_coerce.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/rel.h" @@ -58,6 +59,9 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, adrel = table_open(AttrDefaultRelationId, RowExclusiveLock); + /* An inherited or copied default now belongs to this column. */ + set_coercion_target(expr, NULL, RelationGetRelid(rel), attnum); + /* * Flatten expression to string form for storage. */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 2f073ddb84a..9322cfec413 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -14947,6 +14947,8 @@ ATPrepAlterColumnType(List **wqueue, else if (tab->relkind == RELKIND_RELATION || tab->relkind == RELKIND_PARTITIONED_TABLE) { + Node *orig_transform; + /* * Set up an expression to transform the old data value to the new * type. If a USING option was given, use the expression as @@ -14964,6 +14966,7 @@ ATPrepAlterColumnType(List **wqueue, 0); } + orig_transform = transform; transform = coerce_to_target_type(pstate, transform, exprType(transform), targettype, targettypmod, @@ -14993,6 +14996,8 @@ ATPrepAlterColumnType(List **wqueue, targettypmod)) : 0)); } + set_coercion_target(transform, orig_transform, RelationGetRelid(rel), attnum); + /* Fix collations after all else */ assign_expr_collations(pstate, transform); @@ -15279,9 +15284,12 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, */ if (attTup->atthasdef) { + Node *orig_default; + defaultexpr = build_column_default(rel, attnum); Assert(defaultexpr); defaultexpr = strip_implicit_coercions(defaultexpr); + orig_default = defaultexpr; defaultexpr = coerce_to_target_type(NULL, /* no UNKNOWN params */ defaultexpr, exprType(defaultexpr), targettype, targettypmod, @@ -15301,6 +15309,8 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, errmsg("default for column \"%s\" cannot be cast automatically to type %s", colName, format_type_be(targettype)))); } + set_coercion_target(defaultexpr, orig_default, + RelationGetRelid(rel), attnum); } else defaultexpr = NULL; diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 99a0ae2228e..5ff7c16aa14 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -901,7 +901,7 @@ DefineDomain(ParseState *pstate, CreateDomainStmt *stmt) basetypeoid, basetypeMod, domainName, - 0); + 0, InvalidOid, InvalidAttrNumber); /* * If the expression is just a NULL constant, we treat it @@ -2701,7 +2701,7 @@ AlterDomainDefault(List *names, Node *defaultRaw) typTup->typbasetype, typTup->typtypmod, NameStr(typTup->typname), - 0); + 0, InvalidOid, InvalidAttrNumber); /* * If the expression is just a NULL constant, we treat the command diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 82e846a1f4f..e095d048b55 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -1196,6 +1196,8 @@ ExecInitExprRec(Expr *node, ExprState *state, ExecInitFunc(&scratch, node, func->args, func->funcid, func->inputcollid, state); + if (OidIsValid(func->functargetrelid)) + scratch.opcode = EEOP_FUNCEXPR_COERCION; ExprEvalPushStep(state, &scratch); break; } diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 397219f7a3a..082f349d606 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -514,6 +514,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_FUNCEXPR_STRICT_2, &&CASE_EEOP_FUNCEXPR_FUSAGE, &&CASE_EEOP_FUNCEXPR_STRICT_FUSAGE, + &&CASE_EEOP_FUNCEXPR_COERCION, &&CASE_EEOP_BOOL_AND_STEP_FIRST, &&CASE_EEOP_BOOL_AND_STEP, &&CASE_EEOP_BOOL_AND_STEP_LAST, @@ -1032,6 +1033,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_FUNCEXPR_COERCION) + { + ExecEvalFuncExprCoercion(state, op, econtext); + + EEO_NEXT(); + } + /* * If any of its clauses is FALSE, an AND's result is FALSE regardless * of the states of the rest of the clauses, so we can stop evaluating @@ -3048,6 +3056,58 @@ ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op, pgstat_end_function_usage(&fcusage, true); } +/* + * Add destination context for an assignment coercion. Resolve names only + * when reporting an error, so stored expressions continue to reflect renames. + * Leave the datatype's primary message and object diagnostics untouched. + */ +static void +coercion_error_callback(void *arg) +{ + FuncExpr *expr = (FuncExpr *) arg; + char *colname; + char *relname; + + Assert(IsA(expr, FuncExpr)); + Assert(OidIsValid(expr->functargetrelid)); + + colname = get_attname(expr->functargetrelid, expr->functargetattnum, true); + if (colname == NULL) + return; + relname = get_rel_name(expr->functargetrelid); + if (relname == NULL) + return; + + errcontext("column \"%s\" of relation \"%s\"", colname, relname); +} + +/* + * Evaluate EEOP_FUNCEXPR_COERCION with destination-column error context. + * Arguments have already been evaluated, so errors in the source expression + * are not attributed to the destination. + */ +void +ExecEvalFuncExprCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext) +{ + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + ErrorContextCallback errcallback; + + errcallback.callback = coercion_error_callback; + errcallback.arg = fcinfo->flinfo->fn_expr; + errcallback.previous = error_context_stack; + error_context_stack = &errcallback; + + /* These helpers also handle disabled function statistics collection. */ + if (fcinfo->flinfo->fn_strict) + ExecEvalFuncExprStrictFusage(state, op, econtext); + else + ExecEvalFuncExprFusage(state, op, econtext); + + /* On ERROR, the surrounding error handler restores the callback stack. */ + error_context_stack = errcallback.previous; +} + /* * Evaluate a PARAM_EXEC parameter. * diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index fc80d3e55fe..1dac4f9b9b9 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -761,6 +761,12 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_FUNCEXPR_COERCION: + build_EvalXFunc(b, mod, "ExecEvalFuncExprCoercion", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + /* * Treat them the same for now, optimizer can remove * redundancy. Could be worthwhile to optimize during emission diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index c8a1f841293..586daf03959 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -151,6 +151,7 @@ void *referenced_functions[] = ExecEvalFieldSelect, ExecEvalFieldStoreDeForm, ExecEvalFieldStoreForm, + ExecEvalFuncExprCoercion, ExecEvalFuncExprFusage, ExecEvalFuncExprStrictFusage, ExecEvalGroupingFunc, diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 3e1f210652d..41af999482e 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -153,6 +153,7 @@ static Expr *simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List **args_p, bool funcvariadic, bool process_args, bool allow_non_const, + FuncExpr *original, eval_const_expressions_context *context); static Node *simplify_aggref(Aggref *aggref, eval_const_expressions_context *context); @@ -168,6 +169,7 @@ static Expr *evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List *args, bool funcvariadic, HeapTuple func_tuple, + FuncExpr *original, eval_const_expressions_context *context); static Expr *inline_function(Oid funcid, Oid result_type, Oid result_collid, Oid input_collid, List *args, @@ -2985,6 +2987,7 @@ eval_const_expressions_mutator(Node *node, expr->funcvariadic, true, true, + expr, context); if (simple) /* successfully simplified it */ return (Node *) simple; @@ -3004,6 +3007,8 @@ eval_const_expressions_mutator(Node *node, newexpr->funccollid = expr->funccollid; newexpr->inputcollid = expr->inputcollid; newexpr->args = args; + newexpr->functargetrelid = expr->functargetrelid; + newexpr->functargetattnum = expr->functargetattnum; newexpr->location = expr->location; return (Node *) newexpr; } @@ -3037,6 +3042,7 @@ eval_const_expressions_mutator(Node *node, false, true, true, + NULL, context); if (simple) /* successfully simplified it */ return (Node *) simple; @@ -3156,6 +3162,7 @@ eval_const_expressions_mutator(Node *node, false, false, false, + NULL, context); if (simple) /* successfully simplified it */ { @@ -3515,6 +3522,7 @@ eval_const_expressions_mutator(Node *node, false, true, true, + NULL, context); if (simple) /* successfully simplified output fn */ { @@ -3547,6 +3555,7 @@ eval_const_expressions_mutator(Node *node, false, false, true, + NULL, context); if (simple) /* successfully simplified input fn */ return (Node *) simple; @@ -4668,7 +4677,8 @@ simplify_boolean_equality(Oid opno, List *args) * polymorphic functions), result typmod, result collation, the input * collation to use for the function, the original argument list (not * const-simplified yet, unless process_args is false), and some flags; - * also the context data for eval_const_expressions. + * also the original FuncExpr (if any), for preserving the coercion target, + * and the context data for eval_const_expressions. * * Returns a simplified expression if successful, or NULL if cannot * simplify the function call. @@ -4685,6 +4695,7 @@ static Expr * simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List **args_p, bool funcvariadic, bool process_args, bool allow_non_const, + FuncExpr *original, eval_const_expressions_context *context) { List *args = *args_p; @@ -4730,7 +4741,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, newexpr = evaluate_function(funcid, result_type, result_typmod, result_collid, input_collid, args, funcvariadic, - func_tuple, context); + func_tuple, original, context); if (!newexpr && allow_non_const && OidIsValid(func_form->prosupport)) { @@ -4753,6 +4764,8 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, fexpr.funccollid = result_collid; fexpr.inputcollid = input_collid; fexpr.args = args; + fexpr.functargetrelid = original ? original->functargetrelid : InvalidOid; + fexpr.functargetattnum = original ? original->functargetattnum : InvalidAttrNumber; fexpr.location = -1; req.type = T_SupportRequestSimplify; @@ -4767,7 +4780,9 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod, Assert(newexpr != (Expr *) &fexpr); } - if (!newexpr && allow_non_const) + /* Inlining would discard the assignment coercion's error context. */ + if (!newexpr && allow_non_const && + !(original && OidIsValid(original->functargetrelid))) newexpr = inline_function(funcid, result_type, result_collid, input_collid, args, funcvariadic, func_tuple, context); @@ -5370,6 +5385,7 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, Oid result_collid, Oid input_collid, List *args, bool funcvariadic, HeapTuple func_tuple, + FuncExpr *original, eval_const_expressions_context *context) { Form_pg_proc funcform = (Form_pg_proc) GETSTRUCT(func_tuple); @@ -5454,6 +5470,11 @@ evaluate_function(Oid funcid, Oid result_type, int32 result_typmod, newexpr->funccollid = result_collid; /* doesn't matter */ newexpr->inputcollid = input_collid; newexpr->args = args; + if (original) + { + newexpr->functargetrelid = original->functargetrelid; + newexpr->functargetattnum = original->functargetattnum; + } newexpr->location = -1; return evaluate_expr((Expr *) newexpr, result_type, result_typmod, diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c index d3240f4b265..cc7c7745d7a 100644 --- a/src/backend/parser/parse_coerce.c +++ b/src/backend/parser/parse_coerce.c @@ -131,6 +131,78 @@ coerce_to_target_type(ParseState *pstate, Node *expr, Oid exprtype, return result; } +/* + * Label assignment length coercions with their destination column. Follow + * only the coercion and assignment nodes added above source: an error in the + * source expression must not be attributed to the destination. Coercion can + * strip off source's CollateExpr wrappers, so recognize its unwrapped form too. + * + * This is deliberately not a general expression-tree walk. Each additional + * case must distinguish assignment-added coercions from the source expression. + * Unsupported shapes are left unannotated. + * + * With source == NULL, only update coercions that already have a target. This + * is used to retarget defaults inherited from another column or table. + * The caller must own the expression tree, since we modify it in place. + */ +void +set_coercion_target(Node *expr, Node *source, Oid relid, AttrNumber attnum) +{ + if (expr == NULL || expr == source) + return; + while (source && IsA(source, CollateExpr)) + { + source = (Node *) ((CollateExpr *) source)->arg; + if (expr == source) + return; + } + + switch (nodeTag(expr)) + { + case T_FuncExpr: + { + FuncExpr *fexpr = (FuncExpr *) expr; + + if (fexpr->funcformat == COERCE_IMPLICIT_CAST && + exprIsLengthCoercion(expr, NULL) && + (source != NULL || OidIsValid(fexpr->functargetrelid))) + { + fexpr->functargetrelid = relid; + fexpr->functargetattnum = attnum; + } + break; + } + case T_CollateExpr: + set_coercion_target((Node *) ((CollateExpr *) expr)->arg, + source, relid, attnum); + break; + case T_RelabelType: + if (((RelabelType *) expr)->relabelformat == COERCE_IMPLICIT_CAST) + set_coercion_target((Node *) ((RelabelType *) expr)->arg, + source, relid, attnum); + break; + case T_CoerceToDomain: + if (((CoerceToDomain *) expr)->coercionformat == COERCE_IMPLICIT_CAST) + set_coercion_target((Node *) ((CoerceToDomain *) expr)->arg, + source, relid, attnum); + break; + case T_ArrayCoerceExpr: + if (((ArrayCoerceExpr *) expr)->coerceformat == COERCE_IMPLICIT_CAST) + set_coercion_target((Node *) ((ArrayCoerceExpr *) expr)->elemexpr, + source, relid, attnum); + break; + case T_FieldStore: + foreach_ptr(Node, value, ((FieldStore *) expr)->newvals) + set_coercion_target(value, source, relid, attnum); + break; + case T_SubscriptingRef: + set_coercion_target((Node *) ((SubscriptingRef *) expr)->refassgnexpr, + source, relid, attnum); + break; + default: + break; + } +} /* * coerce_type() diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 728936b502e..66518de2043 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -466,6 +466,7 @@ transformAssignedExpr(ParseState *pstate, int32 attrtypmod; Oid attrcollation; /* collation of target column */ ParseExprKind sv_expr_kind; + Node *orig_expr = (Node *) expr; /* * Save and restore identity of expression type we're parsing. We must @@ -576,8 +577,6 @@ transformAssignedExpr(ParseState *pstate, * For normal non-qualified target column, do type checking and * coercion. */ - Node *orig_expr = (Node *) expr; - expr = (Expr *) coerce_to_target_type(pstate, orig_expr, type_id, @@ -597,6 +596,9 @@ transformAssignedExpr(ParseState *pstate, parser_errposition(pstate, exprLocation(orig_expr)))); } + set_coercion_target((Node *) expr, orig_expr, + RelationGetRelid(rd), attrno); + pstate->p_expr_kind = sv_expr_kind; return expr; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 27964cb83c6..bce08a5c806 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1282,6 +1282,7 @@ build_column_default(Relation rel, int attrno) int32 atttypmod = att_tup->atttypmod; Node *expr = NULL; Oid exprtype; + Node *orig_expr; if (att_tup->attidentity) { @@ -1321,6 +1322,7 @@ build_column_default(Relation rel, int attrno) * the parser's processing of non-defaulted expressions --- see * transformAssignedExpr(). */ + orig_expr = expr; exprtype = exprType(expr); expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */ @@ -1339,6 +1341,8 @@ build_column_default(Relation rel, int attrno) format_type_be(exprtype)), errhint("You will need to rewrite or cast the expression."))); + set_coercion_target(expr, orig_expr, RelationGetRelid(rel), attrno); + return expr; } diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 6f3e526de96..7d9065c51de 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202609152 +#define CATALOG_VERSION_NO 202609271 #endif diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 6c9ac812aa0..7948a662076 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -131,7 +131,8 @@ extern Node *cookDefault(ParseState *pstate, Oid atttypid, int32 atttypmod, const char *attname, - char attgenerated); + char attgenerated, + Oid relid, AttrNumber attnum); extern void DeleteRelationTuple(Oid relid); extern void DeleteAttributeTuples(Oid relid); diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index c61b3d624d5..d9ba1c66397 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -125,6 +125,7 @@ typedef enum ExprEvalOp EEOP_FUNCEXPR_STRICT_2, EEOP_FUNCEXPR_FUSAGE, EEOP_FUNCEXPR_STRICT_FUSAGE, + EEOP_FUNCEXPR_COERCION, /* * Evaluate boolean AND expression, one step per subexpression. FIRST/LAST @@ -856,6 +857,8 @@ extern void ExecEvalFuncExprFusage(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalFuncExprStrictFusage(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalFuncExprCoercion(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext); extern void ExecEvalParamSet(ExprState *state, ExprEvalStep *op, diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 2a832a27f49..626d60820ff 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -790,6 +790,11 @@ typedef struct FuncExpr Oid inputcollid pg_node_attr(query_jumble_ignore); /* arguments to the function */ List *args; + + /* destination column of an assignment coercion */ + Oid functargetrelid pg_node_attr(equal_ignore, query_jumble_ignore); + AttrNumber functargetattnum pg_node_attr(equal_ignore, query_jumble_ignore); + /* token location, or -1 if unknown */ ParseLoc location; } FuncExpr; diff --git a/src/include/parser/parse_coerce.h b/src/include/parser/parse_coerce.h index aabacd49b65..b3be6326283 100644 --- a/src/include/parser/parse_coerce.h +++ b/src/include/parser/parse_coerce.h @@ -45,6 +45,8 @@ extern Node *coerce_to_target_type(ParseState *pstate, int location); extern bool can_coerce_type(int nargs, const Oid *input_typeids, const Oid *target_typeids, CoercionContext ccontext); +extern void set_coercion_target(Node *expr, Node *source, + Oid relid, AttrNumber attnum); extern Node *coerce_type(ParseState *pstate, Node *node, Oid inputTypeId, Oid targetTypeId, int32 targetTypeMod, CoercionContext ccontext, CoercionForm cformat, int location); diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index 66439d427a3..e4b9d99081d 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -30,6 +30,7 @@ UPDATE arrtest SET e[1] = '2.2'; INSERT INTO arrtest (f) VALUES ('{"too long"}'); ERROR: value too long for type character(5) +CONTEXT: column "f" of relation "arrtest" INSERT INTO arrtest (a, b[1:2][1:2], c, d, e, f, g) VALUES ('{11,12,23}', '{{3,4},{4,5}}', '{"foobar"}', '{{"elt1", "elt2"}}', '{"3.4", "6.7"}', diff --git a/src/test/regress/expected/bit.out b/src/test/regress/expected/bit.out index 6a436288bb1..48aa1840f5d 100644 --- a/src/test/regress/expected/bit.out +++ b/src/test/regress/expected/bit.out @@ -7,11 +7,13 @@ CREATE TABLE BIT_TABLE(b BIT(11)); INSERT INTO BIT_TABLE VALUES (B'10'); -- too short ERROR: bit string length 2 does not match type bit(11) +CONTEXT: column "b" of relation "bit_table" INSERT INTO BIT_TABLE VALUES (B'00000000000'); INSERT INTO BIT_TABLE VALUES (B'11011000000'); INSERT INTO BIT_TABLE VALUES (B'01010101010'); INSERT INTO BIT_TABLE VALUES (B'101011111010'); -- too long ERROR: bit string length 12 does not match type bit(11) +CONTEXT: column "b" of relation "bit_table" --INSERT INTO BIT_TABLE VALUES ('X554'); --INSERT INTO BIT_TABLE VALUES ('X555'); SELECT * FROM BIT_TABLE; @@ -29,6 +31,7 @@ INSERT INTO VARBIT_TABLE VALUES (B'010101'); INSERT INTO VARBIT_TABLE VALUES (B'01010101010'); INSERT INTO VARBIT_TABLE VALUES (B'101011111010'); -- too long ERROR: bit string too long for type bit varying(11) +CONTEXT: column "v" of relation "varbit_table" --INSERT INTO VARBIT_TABLE VALUES ('X554'); --INSERT INTO VARBIT_TABLE VALUES ('X555'); SELECT * FROM VARBIT_TABLE; diff --git a/src/test/regress/expected/char.out b/src/test/regress/expected/char.out index 4df596d111d..a20f07d189b 100644 --- a/src/test/regress/expected/char.out +++ b/src/test/regress/expected/char.out @@ -25,6 +25,7 @@ INSERT INTO CHAR_TBL (f1) VALUES (''); -- try char's of greater than 1 length INSERT INTO CHAR_TBL (f1) VALUES ('cd'); ERROR: value too long for type character(1) +CONTEXT: column "f1" of relation "char_tbl" INSERT INTO CHAR_TBL (f1) VALUES ('c '); SELECT * FROM CHAR_TBL; f1 @@ -110,6 +111,7 @@ DROP TABLE CHAR_TBL; -- INSERT INTO CHAR_TBL (f1) VALUES ('abcde'); ERROR: value too long for type character(4) +CONTEXT: column "f1" of relation "char_tbl" SELECT * FROM CHAR_TBL; f1 ------ diff --git a/src/test/regress/expected/char_1.out b/src/test/regress/expected/char_1.out index 3add81e9f0f..7dd071b9051 100644 --- a/src/test/regress/expected/char_1.out +++ b/src/test/regress/expected/char_1.out @@ -25,6 +25,7 @@ INSERT INTO CHAR_TBL (f1) VALUES (''); -- try char's of greater than 1 length INSERT INTO CHAR_TBL (f1) VALUES ('cd'); ERROR: value too long for type character(1) +CONTEXT: column "f1" of relation "char_tbl" INSERT INTO CHAR_TBL (f1) VALUES ('c '); SELECT * FROM CHAR_TBL; f1 @@ -110,6 +111,7 @@ DROP TABLE CHAR_TBL; -- INSERT INTO CHAR_TBL (f1) VALUES ('abcde'); ERROR: value too long for type character(4) +CONTEXT: column "f1" of relation "char_tbl" SELECT * FROM CHAR_TBL; f1 ------ diff --git a/src/test/regress/expected/char_2.out b/src/test/regress/expected/char_2.out index ebde0f3e59f..c31f0b892a5 100644 --- a/src/test/regress/expected/char_2.out +++ b/src/test/regress/expected/char_2.out @@ -25,6 +25,7 @@ INSERT INTO CHAR_TBL (f1) VALUES (''); -- try char's of greater than 1 length INSERT INTO CHAR_TBL (f1) VALUES ('cd'); ERROR: value too long for type character(1) +CONTEXT: column "f1" of relation "char_tbl" INSERT INTO CHAR_TBL (f1) VALUES ('c '); SELECT * FROM CHAR_TBL; f1 @@ -110,6 +111,7 @@ DROP TABLE CHAR_TBL; -- INSERT INTO CHAR_TBL (f1) VALUES ('abcde'); ERROR: value too long for type character(4) +CONTEXT: column "f1" of relation "char_tbl" SELECT * FROM CHAR_TBL; f1 ------ diff --git a/src/test/regress/expected/copy.out b/src/test/regress/expected/copy.out index 0af0b646921..ce46a7bfb55 100644 --- a/src/test/regress/expected/copy.out +++ b/src/test/regress/expected/copy.out @@ -518,9 +518,11 @@ copy oversized_column_default from stdin; -- error if the column is excluded copy oversized_column_default (col2) from stdin; ERROR: value too long for type character varying(5) +CONTEXT: column "col1" of relation "oversized_column_default" -- error if the DEFAULT option is given copy oversized_column_default from stdin (default ''); ERROR: value too long for type character varying(5) +CONTEXT: column "col1" of relation "oversized_column_default" drop table oversized_column_default; -- -- Create partitioned table that does not allow bulk insertions, to test bugs diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index 62a48a523a2..bb2b1b2c6e8 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -98,6 +98,7 @@ create table basictest INSERT INTO basictest values ('88', 'haha', 'short', '123.12'); -- Good INSERT INTO basictest values ('88', 'haha', 'short text', '123.12'); -- Bad varchar ERROR: value too long for type character varying(5) +CONTEXT: column "testvarchar" of relation "basictest" INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate numeric -- Test copy COPY basictest (testvarchar) FROM stdin; -- fail @@ -204,6 +205,7 @@ INSERT INTO domarrtest values ('{2,2}', '{{"a"},{"c"}}'); INSERT INTO domarrtest values (NULL, '{{"a","b","c"},{"d","e","f"}}'); INSERT INTO domarrtest values (NULL, '{{"toolong","b","c"},{"d","e","f"}}'); ERROR: value too long for type character varying(4) +CONTEXT: column "testchar4arr" of relation "domarrtest" INSERT INTO domarrtest (testint4arr[1], testint4arr[3]) values (11,22); select * from domarrtest; testint4arr | testchar4arr @@ -568,6 +570,7 @@ create domain vc4 as varchar(4); create table vc4table (f1 vc4[]); insert into vc4table values(array['too long']); -- fail ERROR: value too long for type character varying(4) +CONTEXT: column "f1" of relation "vc4table" insert into vc4table values(array['too long']::vc4[]); -- cast truncates select * from vc4table; f1 @@ -997,8 +1000,10 @@ create temp table dtest(f1 dtop); insert into dtest values('x123'); insert into dtest values('x1234'); -- fail, implicit coercion ERROR: value too long for type character varying(4) +CONTEXT: column "f1" of relation "dtest" insert into dtest values('y1234'); -- fail, implicit coercion ERROR: value too long for type character varying(4) +CONTEXT: column "f1" of relation "dtest" insert into dtest values('y123'); -- fail ERROR: value for domain dtop violates check constraint "dinter_check" insert into dtest values('yz23'); -- fail diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out index c58e232a263..4c33dba43c1 100644 --- a/src/test/regress/expected/numeric.out +++ b/src/test/regress/expected/numeric.out @@ -1127,20 +1127,24 @@ INSERT INTO fract_only VALUES (2, '0.1'); INSERT INTO fract_only VALUES (3, '1.0'); -- should fail ERROR: numeric field overflow DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1. +CONTEXT: column "val" of relation "fract_only" INSERT INTO fract_only VALUES (4, '-0.9999'); INSERT INTO fract_only VALUES (5, '0.99994'); INSERT INTO fract_only VALUES (6, '0.99995'); -- should fail ERROR: numeric field overflow DETAIL: A field with precision 4, scale 4 must round to an absolute value less than 1. +CONTEXT: column "val" of relation "fract_only" INSERT INTO fract_only VALUES (7, '0.00001'); INSERT INTO fract_only VALUES (8, '0.00017'); INSERT INTO fract_only VALUES (9, 'NaN'); INSERT INTO fract_only VALUES (10, 'Inf'); -- should fail ERROR: numeric field overflow DETAIL: A field with precision 4, scale 4 cannot hold an infinite value. +CONTEXT: column "val" of relation "fract_only" INSERT INTO fract_only VALUES (11, '-Inf'); -- should fail ERROR: numeric field overflow DETAIL: A field with precision 4, scale 4 cannot hold an infinite value. +CONTEXT: column "val" of relation "fract_only" SELECT * FROM fract_only; id | val ----+--------- @@ -2739,21 +2743,27 @@ SELECT scale(millions), * FROM num_typemod_test ORDER BY millions; INSERT INTO num_typemod_test (millions) VALUES ('inf'); ERROR: numeric field overflow DETAIL: A field with precision 3, scale -6 cannot hold an infinite value. +CONTEXT: column "millions" of relation "num_typemod_test" INSERT INTO num_typemod_test (millions) VALUES (999500000); ERROR: numeric field overflow DETAIL: A field with precision 3, scale -6 must round to an absolute value less than 10^9. +CONTEXT: column "millions" of relation "num_typemod_test" INSERT INTO num_typemod_test (thousands) VALUES (999500); ERROR: numeric field overflow DETAIL: A field with precision 3, scale -3 must round to an absolute value less than 10^6. +CONTEXT: column "thousands" of relation "num_typemod_test" INSERT INTO num_typemod_test (units) VALUES (999.5); ERROR: numeric field overflow DETAIL: A field with precision 3, scale 0 must round to an absolute value less than 10^3. +CONTEXT: column "units" of relation "num_typemod_test" INSERT INTO num_typemod_test (thousandths) VALUES (0.9995); ERROR: numeric field overflow DETAIL: A field with precision 3, scale 3 must round to an absolute value less than 1. +CONTEXT: column "thousandths" of relation "num_typemod_test" INSERT INTO num_typemod_test (millionths) VALUES (0.0009995); ERROR: numeric field overflow DETAIL: A field with precision 3, scale 6 must round to an absolute value less than 10^-3. +CONTEXT: column "millionths" of relation "num_typemod_test" -- -- Test some corner cases for multiplication -- diff --git a/src/test/regress/expected/varchar.out b/src/test/regress/expected/varchar.out index 28863529a6b..7bac6cbdd40 100644 --- a/src/test/regress/expected/varchar.out +++ b/src/test/regress/expected/varchar.out @@ -17,6 +17,7 @@ INSERT INTO VARCHAR_TBL (f1) VALUES (''); -- try varchar's of greater than 1 length INSERT INTO VARCHAR_TBL (f1) VALUES ('cd'); ERROR: value too long for type character varying(1) +CONTEXT: column "f1" of relation "varchar_tbl" INSERT INTO VARCHAR_TBL (f1) VALUES ('c '); SELECT * FROM VARCHAR_TBL; f1 @@ -102,6 +103,7 @@ DROP TABLE VARCHAR_TBL; -- INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde'); ERROR: value too long for type character varying(4) +CONTEXT: column "f1" of relation "varchar_tbl" SELECT * FROM VARCHAR_TBL; f1 ------ @@ -130,3 +132,222 @@ SELECT * FROM pg_input_error_info('abcde', 'varchar(4)'); value too long for type character varying(4) | | | 22001 (1 row) +-- report the destination column for planning-time and runtime errors +CREATE TEMP TABLE varchar_target (id int, username varchar(10), email varchar(10)); +INSERT INTO varchar_target (email, username) VALUES ('123456789012', 'ok'); +ERROR: value too long for type character varying(10) +CONTEXT: column "email" of relation "varchar_target" +INSERT INTO varchar_target (username) VALUES ('ok'), ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +CREATE TEMP TABLE varchar_source (v text); +INSERT INTO varchar_source VALUES ('123456789012'); +INSERT INTO varchar_target VALUES (1, 'ok', 'ok'); +UPDATE varchar_target SET username = (SELECT v FROM varchar_source); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +-- keep the destination in generic plans and use the current column name +SET plan_cache_mode = force_generic_plan; +PREPARE varchar_insert(text) AS + INSERT INTO varchar_target VALUES (2, $1, NULL); +EXECUTE varchar_insert('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +ALTER TABLE varchar_target RENAME COLUMN username TO "User Name"; +EXECUTE varchar_insert('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "User Name" of relation "varchar_target" +DEALLOCATE varchar_insert; +RESET plan_cache_mode; +-- keep the column identity in stored SQL bodies across renames +CREATE FUNCTION pg_temp.varchar_insert_fn(text) RETURNS void +LANGUAGE SQL BEGIN ATOMIC + INSERT INTO varchar_target VALUES (3, $1, NULL); +END; +ALTER TABLE varchar_target RENAME COLUMN "User Name" TO username; +SELECT pg_temp.varchar_insert_fn('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +SQL function "varchar_insert_fn" statement 1 +DROP FUNCTION pg_temp.varchar_insert_fn(text); +-- runtime diagnostics and callback cleanup +DO $$ +DECLARE + message text; + context text; + colname text; + tabname text; + schemaname text; +BEGIN + BEGIN + INSERT INTO varchar_target (email) SELECT v FROM varchar_source; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS message = MESSAGE_TEXT, + context = PG_EXCEPTION_CONTEXT, + colname = COLUMN_NAME, + tabname = TABLE_NAME, + schemaname = SCHEMA_NAME; + RAISE NOTICE 'generic error: %, destination context: %, object fields empty: %', + message = 'value too long for type character varying(10)', + position('column "email" of relation "varchar_target"' in context) = 1, + colname = '' AND tabname = '' AND schemaname = ''; + END; + + -- Context must not leak after an error, a NULL input, or a successful call. + -- Source argument errors must not acquire the destination context. + INSERT INTO varchar_target (username) SELECT NULLIF(v, v) FROM varchar_source; + BEGIN + INSERT INTO varchar_target (username, email) + VALUES ((SELECT 'ok'::text FROM varchar_source), + pg_catalog.varchar((SELECT v FROM varchar_source), 15, false)); + RAISE EXCEPTION 'expected a source length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'unexpected assignment context: %', context; + END IF; + END; + BEGIN + PERFORM pg_catalog.varchar((SELECT v FROM varchar_source), 14, false); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'assignment context leaked: %', context; + END IF; + END; +END; +$$; +NOTICE: generic error: t, destination context: t, object fields empty: t +-- collation and array subscript assignments +INSERT INTO varchar_target (username) VALUES ('123456789012' COLLATE "C"); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +CREATE TEMP TABLE varchar_nested (a varchar(10)[]); +INSERT INTO varchar_nested (a) VALUES (ARRAY['ok']); +UPDATE varchar_nested SET a[1] = (SELECT v FROM varchar_source); +ERROR: value too long for type character varying(10) +CONTEXT: column "a" of relation "varchar_nested" +-- composite literals and row coercions currently omit the column +-- direct field assignments report the containing column +CREATE TYPE varchar_composite AS (v varchar(10)); +CREATE TEMP TABLE varchar_composite_target (c varchar_composite); +INSERT INTO varchar_composite_target VALUES ('(123456789012)'); +ERROR: value too long for type character varying(10) +LINE 1: INSERT INTO varchar_composite_target VALUES ('(123456789012)... + ^ +INSERT INTO varchar_composite_target VALUES (ROW('123456789012')); +ERROR: value too long for type character varying(10) +INSERT INTO varchar_composite_target (c.v) VALUES ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "c" of relation "varchar_composite_target" +DROP TABLE varchar_composite_target; +DROP TYPE varchar_composite; +-- errors in the source expression must not name the destination +INSERT INTO varchar_target (username) + VALUES (pg_catalog.varchar('123456789012', 5, false)); +ERROR: value too long for type character varying(1) +-- XMLSERIALIZE coercions belong to the source expression +-- skip in builds without XML support +DO $$ +DECLARE + context text; +BEGIN + BEGIN + PERFORM XMLPARSE(CONTENT ''); + EXCEPTION WHEN feature_not_supported THEN + RETURN; + END; + CREATE TEMP TABLE varchar_unbounded ( + v text DEFAULT XMLSERIALIZE(CONTENT 'long' AS varchar(3))); + BEGIN + INSERT INTO varchar_unbounded + VALUES (XMLSERIALIZE(CONTENT 'long' AS varchar(3)) COLLATE "C"); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'source error attributed to column %', context; + END IF; + END; + BEGIN + INSERT INTO varchar_unbounded DEFAULT VALUES; + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'default source error attributed to column %', context; + END IF; + END; + DROP TABLE varchar_unbounded; +END; +$$; +-- column defaults and stored generated columns +CREATE TEMP TABLE varchar_default ( + username varchar(10) DEFAULT repeat('x', 11 + (random() * 0)::int)); +INSERT INTO varchar_default DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_default" +CREATE TEMP TABLE varchar_generated ( + v text, username varchar(10) GENERATED ALWAYS AS (v) STORED); +INSERT INTO varchar_generated (v) VALUES ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_generated" +-- domain defaults currently omit the destination column +-- random() prevents constant folding; the default always produces 11 characters +CREATE DOMAIN varchar_default_domain AS varchar(10) + DEFAULT repeat('x', 11 + (random() * 0)::int); +CREATE TEMP TABLE varchar_domain_default (d varchar_default_domain); +INSERT INTO varchar_domain_default DEFAULT VALUES; +ERROR: value too long for type character varying(10) +DROP TABLE varchar_domain_default; +DROP DOMAIN varchar_default_domain; +-- copied defaults must refer to the new column +CREATE TEMP TABLE varchar_default_copy (LIKE varchar_default INCLUDING DEFAULTS); +DROP TABLE varchar_default; +ALTER TABLE varchar_default_copy RENAME COLUMN username TO copied_name; +INSERT INTO varchar_default_copy DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "copied_name" of relation "varchar_default_copy" +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(12); +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(10); +INSERT INTO varchar_default_copy DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "copied_name" of relation "varchar_default_copy" +-- diagnostic metadata must not affect equality of inherited defaults +CREATE TEMP TABLE varchar_parent1 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_parent2 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_child () INHERITS (varchar_parent1, varchar_parent2); +NOTICE: merging multiple inherited definitions of column "v" +INSERT INTO varchar_child DEFAULT VALUES; +SELECT * FROM varchar_child; + v +---- + ok +(1 row) + +DROP TABLE varchar_child, varchar_parent1, varchar_parent2; +-- report the destination for column type changes +ALTER TABLE varchar_source ALTER COLUMN v TYPE varchar(10); +ERROR: value too long for type character varying(10) +CONTEXT: column "v" of relation "varchar_source" +-- SQL coercion inlining must not discard destination context +-- roll back the cast so it is never visible to other regression sessions +BEGIN; +CREATE FUNCTION pg_temp.varchar_sql_coercion(integer, integer) +RETURNS varchar LANGUAGE SQL IMMUTABLE STRICT +RETURN ($1 / ($2 - 9))::text::varchar; +CREATE CAST (integer AS varchar) +WITH FUNCTION pg_temp.varchar_sql_coercion(integer, integer) AS ASSIGNMENT; +CREATE TEMP TABLE varchar_sql_source (n integer); +INSERT INTO varchar_sql_source VALUES (1), (NULL); +CREATE TEMP TABLE varchar_sql_target (v varchar(5)); +-- strict coercions must skip the function for a NULL argument +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n IS NULL; +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n = 1; +ERROR: division by zero +CONTEXT: SQL function "varchar_sql_coercion" statement 1 +column "v" of relation "varchar_sql_target" +ROLLBACK; +DROP TABLE varchar_source, varchar_target, varchar_nested, + varchar_default_copy, varchar_generated; diff --git a/src/test/regress/expected/varchar_1.out b/src/test/regress/expected/varchar_1.out index 7cb74c752a4..6fe91191336 100644 --- a/src/test/regress/expected/varchar_1.out +++ b/src/test/regress/expected/varchar_1.out @@ -17,6 +17,7 @@ INSERT INTO VARCHAR_TBL (f1) VALUES (''); -- try varchar's of greater than 1 length INSERT INTO VARCHAR_TBL (f1) VALUES ('cd'); ERROR: value too long for type character varying(1) +CONTEXT: column "f1" of relation "varchar_tbl" INSERT INTO VARCHAR_TBL (f1) VALUES ('c '); SELECT * FROM VARCHAR_TBL; f1 @@ -102,6 +103,7 @@ DROP TABLE VARCHAR_TBL; -- INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde'); ERROR: value too long for type character varying(4) +CONTEXT: column "f1" of relation "varchar_tbl" SELECT * FROM VARCHAR_TBL; f1 ------ @@ -130,3 +132,222 @@ SELECT * FROM pg_input_error_info('abcde', 'varchar(4)'); value too long for type character varying(4) | | | 22001 (1 row) +-- report the destination column for planning-time and runtime errors +CREATE TEMP TABLE varchar_target (id int, username varchar(10), email varchar(10)); +INSERT INTO varchar_target (email, username) VALUES ('123456789012', 'ok'); +ERROR: value too long for type character varying(10) +CONTEXT: column "email" of relation "varchar_target" +INSERT INTO varchar_target (username) VALUES ('ok'), ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +CREATE TEMP TABLE varchar_source (v text); +INSERT INTO varchar_source VALUES ('123456789012'); +INSERT INTO varchar_target VALUES (1, 'ok', 'ok'); +UPDATE varchar_target SET username = (SELECT v FROM varchar_source); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +-- keep the destination in generic plans and use the current column name +SET plan_cache_mode = force_generic_plan; +PREPARE varchar_insert(text) AS + INSERT INTO varchar_target VALUES (2, $1, NULL); +EXECUTE varchar_insert('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +ALTER TABLE varchar_target RENAME COLUMN username TO "User Name"; +EXECUTE varchar_insert('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "User Name" of relation "varchar_target" +DEALLOCATE varchar_insert; +RESET plan_cache_mode; +-- keep the column identity in stored SQL bodies across renames +CREATE FUNCTION pg_temp.varchar_insert_fn(text) RETURNS void +LANGUAGE SQL BEGIN ATOMIC + INSERT INTO varchar_target VALUES (3, $1, NULL); +END; +ALTER TABLE varchar_target RENAME COLUMN "User Name" TO username; +SELECT pg_temp.varchar_insert_fn('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +SQL function "varchar_insert_fn" statement 1 +DROP FUNCTION pg_temp.varchar_insert_fn(text); +-- runtime diagnostics and callback cleanup +DO $$ +DECLARE + message text; + context text; + colname text; + tabname text; + schemaname text; +BEGIN + BEGIN + INSERT INTO varchar_target (email) SELECT v FROM varchar_source; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS message = MESSAGE_TEXT, + context = PG_EXCEPTION_CONTEXT, + colname = COLUMN_NAME, + tabname = TABLE_NAME, + schemaname = SCHEMA_NAME; + RAISE NOTICE 'generic error: %, destination context: %, object fields empty: %', + message = 'value too long for type character varying(10)', + position('column "email" of relation "varchar_target"' in context) = 1, + colname = '' AND tabname = '' AND schemaname = ''; + END; + + -- Context must not leak after an error, a NULL input, or a successful call. + -- Source argument errors must not acquire the destination context. + INSERT INTO varchar_target (username) SELECT NULLIF(v, v) FROM varchar_source; + BEGIN + INSERT INTO varchar_target (username, email) + VALUES ((SELECT 'ok'::text FROM varchar_source), + pg_catalog.varchar((SELECT v FROM varchar_source), 15, false)); + RAISE EXCEPTION 'expected a source length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'unexpected assignment context: %', context; + END IF; + END; + BEGIN + PERFORM pg_catalog.varchar((SELECT v FROM varchar_source), 14, false); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'assignment context leaked: %', context; + END IF; + END; +END; +$$; +NOTICE: generic error: t, destination context: t, object fields empty: t +-- collation and array subscript assignments +INSERT INTO varchar_target (username) VALUES ('123456789012' COLLATE "C"); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +CREATE TEMP TABLE varchar_nested (a varchar(10)[]); +INSERT INTO varchar_nested (a) VALUES (ARRAY['ok']); +UPDATE varchar_nested SET a[1] = (SELECT v FROM varchar_source); +ERROR: value too long for type character varying(10) +CONTEXT: column "a" of relation "varchar_nested" +-- composite literals and row coercions currently omit the column +-- direct field assignments report the containing column +CREATE TYPE varchar_composite AS (v varchar(10)); +CREATE TEMP TABLE varchar_composite_target (c varchar_composite); +INSERT INTO varchar_composite_target VALUES ('(123456789012)'); +ERROR: value too long for type character varying(10) +LINE 1: INSERT INTO varchar_composite_target VALUES ('(123456789012)... + ^ +INSERT INTO varchar_composite_target VALUES (ROW('123456789012')); +ERROR: value too long for type character varying(10) +INSERT INTO varchar_composite_target (c.v) VALUES ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "c" of relation "varchar_composite_target" +DROP TABLE varchar_composite_target; +DROP TYPE varchar_composite; +-- errors in the source expression must not name the destination +INSERT INTO varchar_target (username) + VALUES (pg_catalog.varchar('123456789012', 5, false)); +ERROR: value too long for type character varying(1) +-- XMLSERIALIZE coercions belong to the source expression +-- skip in builds without XML support +DO $$ +DECLARE + context text; +BEGIN + BEGIN + PERFORM XMLPARSE(CONTENT ''); + EXCEPTION WHEN feature_not_supported THEN + RETURN; + END; + CREATE TEMP TABLE varchar_unbounded ( + v text DEFAULT XMLSERIALIZE(CONTENT 'long' AS varchar(3))); + BEGIN + INSERT INTO varchar_unbounded + VALUES (XMLSERIALIZE(CONTENT 'long' AS varchar(3)) COLLATE "C"); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'source error attributed to column %', context; + END IF; + END; + BEGIN + INSERT INTO varchar_unbounded DEFAULT VALUES; + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'default source error attributed to column %', context; + END IF; + END; + DROP TABLE varchar_unbounded; +END; +$$; +-- column defaults and stored generated columns +CREATE TEMP TABLE varchar_default ( + username varchar(10) DEFAULT repeat('x', 11 + (random() * 0)::int)); +INSERT INTO varchar_default DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_default" +CREATE TEMP TABLE varchar_generated ( + v text, username varchar(10) GENERATED ALWAYS AS (v) STORED); +INSERT INTO varchar_generated (v) VALUES ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_generated" +-- domain defaults currently omit the destination column +-- random() prevents constant folding; the default always produces 11 characters +CREATE DOMAIN varchar_default_domain AS varchar(10) + DEFAULT repeat('x', 11 + (random() * 0)::int); +CREATE TEMP TABLE varchar_domain_default (d varchar_default_domain); +INSERT INTO varchar_domain_default DEFAULT VALUES; +ERROR: value too long for type character varying(10) +DROP TABLE varchar_domain_default; +DROP DOMAIN varchar_default_domain; +-- copied defaults must refer to the new column +CREATE TEMP TABLE varchar_default_copy (LIKE varchar_default INCLUDING DEFAULTS); +DROP TABLE varchar_default; +ALTER TABLE varchar_default_copy RENAME COLUMN username TO copied_name; +INSERT INTO varchar_default_copy DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "copied_name" of relation "varchar_default_copy" +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(12); +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(10); +INSERT INTO varchar_default_copy DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "copied_name" of relation "varchar_default_copy" +-- diagnostic metadata must not affect equality of inherited defaults +CREATE TEMP TABLE varchar_parent1 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_parent2 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_child () INHERITS (varchar_parent1, varchar_parent2); +NOTICE: merging multiple inherited definitions of column "v" +INSERT INTO varchar_child DEFAULT VALUES; +SELECT * FROM varchar_child; + v +---- + ok +(1 row) + +DROP TABLE varchar_child, varchar_parent1, varchar_parent2; +-- report the destination for column type changes +ALTER TABLE varchar_source ALTER COLUMN v TYPE varchar(10); +ERROR: value too long for type character varying(10) +CONTEXT: column "v" of relation "varchar_source" +-- SQL coercion inlining must not discard destination context +-- roll back the cast so it is never visible to other regression sessions +BEGIN; +CREATE FUNCTION pg_temp.varchar_sql_coercion(integer, integer) +RETURNS varchar LANGUAGE SQL IMMUTABLE STRICT +RETURN ($1 / ($2 - 9))::text::varchar; +CREATE CAST (integer AS varchar) +WITH FUNCTION pg_temp.varchar_sql_coercion(integer, integer) AS ASSIGNMENT; +CREATE TEMP TABLE varchar_sql_source (n integer); +INSERT INTO varchar_sql_source VALUES (1), (NULL); +CREATE TEMP TABLE varchar_sql_target (v varchar(5)); +-- strict coercions must skip the function for a NULL argument +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n IS NULL; +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n = 1; +ERROR: division by zero +CONTEXT: SQL function "varchar_sql_coercion" statement 1 +column "v" of relation "varchar_sql_target" +ROLLBACK; +DROP TABLE varchar_source, varchar_target, varchar_nested, + varchar_default_copy, varchar_generated; diff --git a/src/test/regress/expected/varchar_2.out b/src/test/regress/expected/varchar_2.out index 9b154c6ca53..ead04cdce10 100644 --- a/src/test/regress/expected/varchar_2.out +++ b/src/test/regress/expected/varchar_2.out @@ -17,6 +17,7 @@ INSERT INTO VARCHAR_TBL (f1) VALUES (''); -- try varchar's of greater than 1 length INSERT INTO VARCHAR_TBL (f1) VALUES ('cd'); ERROR: value too long for type character varying(1) +CONTEXT: column "f1" of relation "varchar_tbl" INSERT INTO VARCHAR_TBL (f1) VALUES ('c '); SELECT * FROM VARCHAR_TBL; f1 @@ -102,6 +103,7 @@ DROP TABLE VARCHAR_TBL; -- INSERT INTO VARCHAR_TBL (f1) VALUES ('abcde'); ERROR: value too long for type character varying(4) +CONTEXT: column "f1" of relation "varchar_tbl" SELECT * FROM VARCHAR_TBL; f1 ------ @@ -130,3 +132,222 @@ SELECT * FROM pg_input_error_info('abcde', 'varchar(4)'); value too long for type character varying(4) | | | 22001 (1 row) +-- report the destination column for planning-time and runtime errors +CREATE TEMP TABLE varchar_target (id int, username varchar(10), email varchar(10)); +INSERT INTO varchar_target (email, username) VALUES ('123456789012', 'ok'); +ERROR: value too long for type character varying(10) +CONTEXT: column "email" of relation "varchar_target" +INSERT INTO varchar_target (username) VALUES ('ok'), ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +CREATE TEMP TABLE varchar_source (v text); +INSERT INTO varchar_source VALUES ('123456789012'); +INSERT INTO varchar_target VALUES (1, 'ok', 'ok'); +UPDATE varchar_target SET username = (SELECT v FROM varchar_source); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +-- keep the destination in generic plans and use the current column name +SET plan_cache_mode = force_generic_plan; +PREPARE varchar_insert(text) AS + INSERT INTO varchar_target VALUES (2, $1, NULL); +EXECUTE varchar_insert('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +ALTER TABLE varchar_target RENAME COLUMN username TO "User Name"; +EXECUTE varchar_insert('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "User Name" of relation "varchar_target" +DEALLOCATE varchar_insert; +RESET plan_cache_mode; +-- keep the column identity in stored SQL bodies across renames +CREATE FUNCTION pg_temp.varchar_insert_fn(text) RETURNS void +LANGUAGE SQL BEGIN ATOMIC + INSERT INTO varchar_target VALUES (3, $1, NULL); +END; +ALTER TABLE varchar_target RENAME COLUMN "User Name" TO username; +SELECT pg_temp.varchar_insert_fn('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +SQL function "varchar_insert_fn" statement 1 +DROP FUNCTION pg_temp.varchar_insert_fn(text); +-- runtime diagnostics and callback cleanup +DO $$ +DECLARE + message text; + context text; + colname text; + tabname text; + schemaname text; +BEGIN + BEGIN + INSERT INTO varchar_target (email) SELECT v FROM varchar_source; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS message = MESSAGE_TEXT, + context = PG_EXCEPTION_CONTEXT, + colname = COLUMN_NAME, + tabname = TABLE_NAME, + schemaname = SCHEMA_NAME; + RAISE NOTICE 'generic error: %, destination context: %, object fields empty: %', + message = 'value too long for type character varying(10)', + position('column "email" of relation "varchar_target"' in context) = 1, + colname = '' AND tabname = '' AND schemaname = ''; + END; + + -- Context must not leak after an error, a NULL input, or a successful call. + -- Source argument errors must not acquire the destination context. + INSERT INTO varchar_target (username) SELECT NULLIF(v, v) FROM varchar_source; + BEGIN + INSERT INTO varchar_target (username, email) + VALUES ((SELECT 'ok'::text FROM varchar_source), + pg_catalog.varchar((SELECT v FROM varchar_source), 15, false)); + RAISE EXCEPTION 'expected a source length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'unexpected assignment context: %', context; + END IF; + END; + BEGIN + PERFORM pg_catalog.varchar((SELECT v FROM varchar_source), 14, false); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'assignment context leaked: %', context; + END IF; + END; +END; +$$; +NOTICE: generic error: t, destination context: t, object fields empty: t +-- collation and array subscript assignments +INSERT INTO varchar_target (username) VALUES ('123456789012' COLLATE "C"); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_target" +CREATE TEMP TABLE varchar_nested (a varchar(10)[]); +INSERT INTO varchar_nested (a) VALUES (ARRAY['ok']); +UPDATE varchar_nested SET a[1] = (SELECT v FROM varchar_source); +ERROR: value too long for type character varying(10) +CONTEXT: column "a" of relation "varchar_nested" +-- composite literals and row coercions currently omit the column +-- direct field assignments report the containing column +CREATE TYPE varchar_composite AS (v varchar(10)); +CREATE TEMP TABLE varchar_composite_target (c varchar_composite); +INSERT INTO varchar_composite_target VALUES ('(123456789012)'); +ERROR: value too long for type character varying(10) +LINE 1: INSERT INTO varchar_composite_target VALUES ('(123456789012)... + ^ +INSERT INTO varchar_composite_target VALUES (ROW('123456789012')); +ERROR: value too long for type character varying(10) +INSERT INTO varchar_composite_target (c.v) VALUES ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "c" of relation "varchar_composite_target" +DROP TABLE varchar_composite_target; +DROP TYPE varchar_composite; +-- errors in the source expression must not name the destination +INSERT INTO varchar_target (username) + VALUES (pg_catalog.varchar('123456789012', 5, false)); +ERROR: value too long for type character varying(1) +-- XMLSERIALIZE coercions belong to the source expression +-- skip in builds without XML support +DO $$ +DECLARE + context text; +BEGIN + BEGIN + PERFORM XMLPARSE(CONTENT ''); + EXCEPTION WHEN feature_not_supported THEN + RETURN; + END; + CREATE TEMP TABLE varchar_unbounded ( + v text DEFAULT XMLSERIALIZE(CONTENT 'long' AS varchar(3))); + BEGIN + INSERT INTO varchar_unbounded + VALUES (XMLSERIALIZE(CONTENT 'long' AS varchar(3)) COLLATE "C"); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'source error attributed to column %', context; + END IF; + END; + BEGIN + INSERT INTO varchar_unbounded DEFAULT VALUES; + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'default source error attributed to column %', context; + END IF; + END; + DROP TABLE varchar_unbounded; +END; +$$; +-- column defaults and stored generated columns +CREATE TEMP TABLE varchar_default ( + username varchar(10) DEFAULT repeat('x', 11 + (random() * 0)::int)); +INSERT INTO varchar_default DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_default" +CREATE TEMP TABLE varchar_generated ( + v text, username varchar(10) GENERATED ALWAYS AS (v) STORED); +INSERT INTO varchar_generated (v) VALUES ('123456789012'); +ERROR: value too long for type character varying(10) +CONTEXT: column "username" of relation "varchar_generated" +-- domain defaults currently omit the destination column +-- random() prevents constant folding; the default always produces 11 characters +CREATE DOMAIN varchar_default_domain AS varchar(10) + DEFAULT repeat('x', 11 + (random() * 0)::int); +CREATE TEMP TABLE varchar_domain_default (d varchar_default_domain); +INSERT INTO varchar_domain_default DEFAULT VALUES; +ERROR: value too long for type character varying(10) +DROP TABLE varchar_domain_default; +DROP DOMAIN varchar_default_domain; +-- copied defaults must refer to the new column +CREATE TEMP TABLE varchar_default_copy (LIKE varchar_default INCLUDING DEFAULTS); +DROP TABLE varchar_default; +ALTER TABLE varchar_default_copy RENAME COLUMN username TO copied_name; +INSERT INTO varchar_default_copy DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "copied_name" of relation "varchar_default_copy" +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(12); +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(10); +INSERT INTO varchar_default_copy DEFAULT VALUES; +ERROR: value too long for type character varying(10) +CONTEXT: column "copied_name" of relation "varchar_default_copy" +-- diagnostic metadata must not affect equality of inherited defaults +CREATE TEMP TABLE varchar_parent1 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_parent2 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_child () INHERITS (varchar_parent1, varchar_parent2); +NOTICE: merging multiple inherited definitions of column "v" +INSERT INTO varchar_child DEFAULT VALUES; +SELECT * FROM varchar_child; + v +---- + ok +(1 row) + +DROP TABLE varchar_child, varchar_parent1, varchar_parent2; +-- report the destination for column type changes +ALTER TABLE varchar_source ALTER COLUMN v TYPE varchar(10); +ERROR: value too long for type character varying(10) +CONTEXT: column "v" of relation "varchar_source" +-- SQL coercion inlining must not discard destination context +-- roll back the cast so it is never visible to other regression sessions +BEGIN; +CREATE FUNCTION pg_temp.varchar_sql_coercion(integer, integer) +RETURNS varchar LANGUAGE SQL IMMUTABLE STRICT +RETURN ($1 / ($2 - 9))::text::varchar; +CREATE CAST (integer AS varchar) +WITH FUNCTION pg_temp.varchar_sql_coercion(integer, integer) AS ASSIGNMENT; +CREATE TEMP TABLE varchar_sql_source (n integer); +INSERT INTO varchar_sql_source VALUES (1), (NULL); +CREATE TEMP TABLE varchar_sql_target (v varchar(5)); +-- strict coercions must skip the function for a NULL argument +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n IS NULL; +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n = 1; +ERROR: division by zero +CONTEXT: SQL function "varchar_sql_coercion" statement 1 +column "v" of relation "varchar_sql_target" +ROLLBACK; +DROP TABLE varchar_source, varchar_target, varchar_nested, + varchar_default_copy, varchar_generated; diff --git a/src/test/regress/sql/varchar.sql b/src/test/regress/sql/varchar.sql index c8aaf93e8a8..09324d07b90 100644 --- a/src/test/regress/sql/varchar.sql +++ b/src/test/regress/sql/varchar.sql @@ -71,3 +71,193 @@ SELECT * FROM VARCHAR_TBL; SELECT pg_input_is_valid('abcd ', 'varchar(4)'); SELECT pg_input_is_valid('abcde', 'varchar(4)'); SELECT * FROM pg_input_error_info('abcde', 'varchar(4)'); + +-- report the destination column for planning-time and runtime errors +CREATE TEMP TABLE varchar_target (id int, username varchar(10), email varchar(10)); +INSERT INTO varchar_target (email, username) VALUES ('123456789012', 'ok'); +INSERT INTO varchar_target (username) VALUES ('ok'), ('123456789012'); + +CREATE TEMP TABLE varchar_source (v text); +INSERT INTO varchar_source VALUES ('123456789012'); +INSERT INTO varchar_target VALUES (1, 'ok', 'ok'); +UPDATE varchar_target SET username = (SELECT v FROM varchar_source); + +-- keep the destination in generic plans and use the current column name +SET plan_cache_mode = force_generic_plan; +PREPARE varchar_insert(text) AS + INSERT INTO varchar_target VALUES (2, $1, NULL); +EXECUTE varchar_insert('123456789012'); +ALTER TABLE varchar_target RENAME COLUMN username TO "User Name"; +EXECUTE varchar_insert('123456789012'); +DEALLOCATE varchar_insert; +RESET plan_cache_mode; + +-- keep the column identity in stored SQL bodies across renames +CREATE FUNCTION pg_temp.varchar_insert_fn(text) RETURNS void +LANGUAGE SQL BEGIN ATOMIC + INSERT INTO varchar_target VALUES (3, $1, NULL); +END; +ALTER TABLE varchar_target RENAME COLUMN "User Name" TO username; +SELECT pg_temp.varchar_insert_fn('123456789012'); +DROP FUNCTION pg_temp.varchar_insert_fn(text); + +-- runtime diagnostics and callback cleanup +DO $$ +DECLARE + message text; + context text; + colname text; + tabname text; + schemaname text; +BEGIN + BEGIN + INSERT INTO varchar_target (email) SELECT v FROM varchar_source; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS message = MESSAGE_TEXT, + context = PG_EXCEPTION_CONTEXT, + colname = COLUMN_NAME, + tabname = TABLE_NAME, + schemaname = SCHEMA_NAME; + RAISE NOTICE 'generic error: %, destination context: %, object fields empty: %', + message = 'value too long for type character varying(10)', + position('column "email" of relation "varchar_target"' in context) = 1, + colname = '' AND tabname = '' AND schemaname = ''; + END; + + -- Context must not leak after an error, a NULL input, or a successful call. + -- Source argument errors must not acquire the destination context. + INSERT INTO varchar_target (username) SELECT NULLIF(v, v) FROM varchar_source; + BEGIN + INSERT INTO varchar_target (username, email) + VALUES ((SELECT 'ok'::text FROM varchar_source), + pg_catalog.varchar((SELECT v FROM varchar_source), 15, false)); + RAISE EXCEPTION 'expected a source length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'unexpected assignment context: %', context; + END IF; + END; + BEGIN + PERFORM pg_catalog.varchar((SELECT v FROM varchar_source), 14, false); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_target"' in context) <> 0 THEN + RAISE EXCEPTION 'assignment context leaked: %', context; + END IF; + END; +END; +$$; + +-- collation and array subscript assignments +INSERT INTO varchar_target (username) VALUES ('123456789012' COLLATE "C"); +CREATE TEMP TABLE varchar_nested (a varchar(10)[]); +INSERT INTO varchar_nested (a) VALUES (ARRAY['ok']); +UPDATE varchar_nested SET a[1] = (SELECT v FROM varchar_source); + +-- composite literals and row coercions currently omit the column +-- direct field assignments report the containing column +CREATE TYPE varchar_composite AS (v varchar(10)); +CREATE TEMP TABLE varchar_composite_target (c varchar_composite); +INSERT INTO varchar_composite_target VALUES ('(123456789012)'); +INSERT INTO varchar_composite_target VALUES (ROW('123456789012')); +INSERT INTO varchar_composite_target (c.v) VALUES ('123456789012'); +DROP TABLE varchar_composite_target; +DROP TYPE varchar_composite; + +-- errors in the source expression must not name the destination +INSERT INTO varchar_target (username) + VALUES (pg_catalog.varchar('123456789012', 5, false)); + +-- XMLSERIALIZE coercions belong to the source expression +-- skip in builds without XML support +DO $$ +DECLARE + context text; +BEGIN + BEGIN + PERFORM XMLPARSE(CONTENT ''); + EXCEPTION WHEN feature_not_supported THEN + RETURN; + END; + CREATE TEMP TABLE varchar_unbounded ( + v text DEFAULT XMLSERIALIZE(CONTENT 'long' AS varchar(3))); + BEGIN + INSERT INTO varchar_unbounded + VALUES (XMLSERIALIZE(CONTENT 'long' AS varchar(3)) COLLATE "C"); + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'source error attributed to column %', context; + END IF; + END; + BEGIN + INSERT INTO varchar_unbounded DEFAULT VALUES; + RAISE EXCEPTION 'expected a length error'; + EXCEPTION WHEN string_data_right_truncation THEN + GET STACKED DIAGNOSTICS context = PG_EXCEPTION_CONTEXT; + IF position('of relation "varchar_unbounded"' in context) <> 0 THEN + RAISE EXCEPTION 'default source error attributed to column %', context; + END IF; + END; + DROP TABLE varchar_unbounded; +END; +$$; + +-- column defaults and stored generated columns +CREATE TEMP TABLE varchar_default ( + username varchar(10) DEFAULT repeat('x', 11 + (random() * 0)::int)); +INSERT INTO varchar_default DEFAULT VALUES; +CREATE TEMP TABLE varchar_generated ( + v text, username varchar(10) GENERATED ALWAYS AS (v) STORED); +INSERT INTO varchar_generated (v) VALUES ('123456789012'); + +-- domain defaults currently omit the destination column +-- random() prevents constant folding; the default always produces 11 characters +CREATE DOMAIN varchar_default_domain AS varchar(10) + DEFAULT repeat('x', 11 + (random() * 0)::int); +CREATE TEMP TABLE varchar_domain_default (d varchar_default_domain); +INSERT INTO varchar_domain_default DEFAULT VALUES; +DROP TABLE varchar_domain_default; +DROP DOMAIN varchar_default_domain; + +-- copied defaults must refer to the new column +CREATE TEMP TABLE varchar_default_copy (LIKE varchar_default INCLUDING DEFAULTS); +DROP TABLE varchar_default; +ALTER TABLE varchar_default_copy RENAME COLUMN username TO copied_name; +INSERT INTO varchar_default_copy DEFAULT VALUES; +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(12); +ALTER TABLE varchar_default_copy ALTER COLUMN copied_name TYPE varchar(10); +INSERT INTO varchar_default_copy DEFAULT VALUES; + +-- diagnostic metadata must not affect equality of inherited defaults +CREATE TEMP TABLE varchar_parent1 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_parent2 (v varchar(10) DEFAULT 'ok'); +CREATE TEMP TABLE varchar_child () INHERITS (varchar_parent1, varchar_parent2); +INSERT INTO varchar_child DEFAULT VALUES; +SELECT * FROM varchar_child; +DROP TABLE varchar_child, varchar_parent1, varchar_parent2; + +-- report the destination for column type changes +ALTER TABLE varchar_source ALTER COLUMN v TYPE varchar(10); + +-- SQL coercion inlining must not discard destination context +-- roll back the cast so it is never visible to other regression sessions +BEGIN; +CREATE FUNCTION pg_temp.varchar_sql_coercion(integer, integer) +RETURNS varchar LANGUAGE SQL IMMUTABLE STRICT +RETURN ($1 / ($2 - 9))::text::varchar; +CREATE CAST (integer AS varchar) +WITH FUNCTION pg_temp.varchar_sql_coercion(integer, integer) AS ASSIGNMENT; +CREATE TEMP TABLE varchar_sql_source (n integer); +INSERT INTO varchar_sql_source VALUES (1), (NULL); +CREATE TEMP TABLE varchar_sql_target (v varchar(5)); +-- strict coercions must skip the function for a NULL argument +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n IS NULL; +INSERT INTO varchar_sql_target SELECT n FROM varchar_sql_source WHERE n = 1; +ROLLBACK; + +DROP TABLE varchar_source, varchar_target, varchar_nested, + varchar_default_copy, varchar_generated; -- 2.53.0