From 206b840780cadabf71489264122b4869f08b9d27 Mon Sep 17 00:00:00 2001 From: "xiao.li" Date: Wed, 18 Mar 2026 18:47:33 +0800 Subject: [PATCH 1/2] planner: fix ErrViewInvalid from view constant folding close pingcap/tidb#65832 --- pkg/planner/core/logical_plan_builder.go | 7 ++++++- tests/integrationtest/r/executor/insert.result | 12 ++++++++++++ tests/integrationtest/t/executor/insert.test | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go index 373a6caaf3da5..fbe7d68216cf2 100644 --- a/pkg/planner/core/logical_plan_builder.go +++ b/pkg/planner/core/logical_plan_builder.go @@ -979,7 +979,12 @@ func (b *PlanBuilder) buildSelection(ctx context.Context, p base.LogicalPlan, wh cnfItems := expression.SplitCNFItems(expr) for _, item := range cnfItems { if con, ok := item.(*expression.Constant); ok && expression.ConstExprConsiderPlanCache(con, useCache) { - ret, _, err := expression.EvalBool(b.ctx.GetExprCtx().GetEvalCtx(), expression.CNFExprs{con}, chunk.Row{}) + // Constant predicate folding during planning should not inherit the + // outer statement's truncate handling. For view expansion inside DML, + // expressions like `WHERE ''` are planned as part of a SELECT and + // should not be rejected as invalid view definitions. + exprCtx := exprctx.CtxWithHandleTruncateErrLevel(b.ctx.GetExprCtx(), errctx.LevelIgnore) + ret, _, err := expression.EvalBool(exprCtx.GetEvalCtx(), expression.CNFExprs{con}, chunk.Row{}) if err != nil { return nil, errors.Trace(err) } diff --git a/tests/integrationtest/r/executor/insert.result b/tests/integrationtest/r/executor/insert.result index e1a2b322c422b..558cd64516245 100644 --- a/tests/integrationtest/r/executor/insert.result +++ b/tests/integrationtest/r/executor/insert.result @@ -2443,3 +2443,15 @@ ts DROP TABLE t; SET @@time_zone = @old_time_zone; SET @@sql_mode = @old_sql_mode; +SET @old_sql_mode_65832 = @@sql_mode; +SET sql_mode = 'STRICT_TRANS_TABLES'; +CREATE TABLE t_65832_0(c0 BLOB(304)); +CREATE TABLE t_65832_1(c0 CHAR DEFAULT '0'); +CREATE VIEW v_65832(c0) AS SELECT 0.9699394901011086 FROM t_65832_0 INNER JOIN t_65832_0 AS t0_alias ON t_65832_0.c0 WHERE ''; +INSERT INTO t_65832_1 VALUES ('v') ON DUPLICATE KEY UPDATE c0=(SELECT true FROM v_65832 CROSS JOIN t_65832_0 ON (true)); +SELECT * FROM t_65832_1; +c0 +v +DROP VIEW v_65832; +DROP TABLE t_65832_0, t_65832_1; +SET sql_mode = @old_sql_mode_65832; diff --git a/tests/integrationtest/t/executor/insert.test b/tests/integrationtest/t/executor/insert.test index d042da0980b63..fab5d0e2a9664 100644 --- a/tests/integrationtest/t/executor/insert.test +++ b/tests/integrationtest/t/executor/insert.test @@ -1783,3 +1783,21 @@ DROP TABLE t; SET @@time_zone = @old_time_zone; SET @@sql_mode = @old_sql_mode; --disable_warnings + +# Regression test for issue #65832: +# INSERT ... ON DUPLICATE KEY UPDATE with a subquery referencing a view whose +# SELECT definition contains a constant boolean expression (e.g. WHERE '') should +# not return ErrViewInvalid when the outer statement is an INSERT in strict SQL mode. +# Root cause: planner-time constant folding inherited the outer INSERT's +# truncate handling, causing WHERE '' to raise ErrTruncatedWrongVal and then +# (incorrectly) surface as ErrViewInvalid during view expansion. +SET @old_sql_mode_65832 = @@sql_mode; +SET sql_mode = 'STRICT_TRANS_TABLES'; +CREATE TABLE t_65832_0(c0 BLOB(304)); +CREATE TABLE t_65832_1(c0 CHAR DEFAULT '0'); +CREATE VIEW v_65832(c0) AS SELECT 0.9699394901011086 FROM t_65832_0 INNER JOIN t_65832_0 AS t0_alias ON t_65832_0.c0 WHERE ''; +INSERT INTO t_65832_1 VALUES ('v') ON DUPLICATE KEY UPDATE c0=(SELECT true FROM v_65832 CROSS JOIN t_65832_0 ON (true)); +SELECT * FROM t_65832_1; +DROP VIEW v_65832; +DROP TABLE t_65832_0, t_65832_1; +SET sql_mode = @old_sql_mode_65832; From 94bfd069dce3a4d98b7b623d01a69b75055426ae Mon Sep 17 00:00:00 2001 From: "xiao.li" Date: Fri, 20 Mar 2026 10:24:00 +0800 Subject: [PATCH 2/2] planner: limit truncate relaxation to view expansion close pingcap/tidb#65832 --- pkg/planner/core/logical_plan_builder.go | 21 +++++++++++++++------ pkg/planner/core/planbuilder.go | 3 +++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pkg/planner/core/logical_plan_builder.go b/pkg/planner/core/logical_plan_builder.go index fbe7d68216cf2..f3b1f8ad06ced 100644 --- a/pkg/planner/core/logical_plan_builder.go +++ b/pkg/planner/core/logical_plan_builder.go @@ -975,16 +975,18 @@ func (b *PlanBuilder) buildSelection(ctx context.Context, p base.LogicalPlan, wh } cnfExpres := make([]expression.Expression, 0) useCache := b.ctx.GetSessionVars().StmtCtx.UseCache() + foldEvalCtx := b.ctx.GetExprCtx().GetEvalCtx() + if b.ignoreTruncateErrForViewPredicateFolding { + // View definitions are built as SELECTs even when they are expanded inside + // an outer DML statement, so only that planner-time predicate folding path + // should ignore truncate errors like `WHERE ''`. + foldEvalCtx = exprctx.CtxWithHandleTruncateErrLevel(b.ctx.GetExprCtx(), errctx.LevelIgnore).GetEvalCtx() + } for _, expr := range expressions { cnfItems := expression.SplitCNFItems(expr) for _, item := range cnfItems { if con, ok := item.(*expression.Constant); ok && expression.ConstExprConsiderPlanCache(con, useCache) { - // Constant predicate folding during planning should not inherit the - // outer statement's truncate handling. For view expansion inside DML, - // expressions like `WHERE ''` are planned as part of a SELECT and - // should not be rejected as invalid view definitions. - exprCtx := exprctx.CtxWithHandleTruncateErrLevel(b.ctx.GetExprCtx(), errctx.LevelIgnore) - ret, _, err := expression.EvalBool(exprCtx.GetEvalCtx(), expression.CNFExprs{con}, chunk.Row{}) + ret, _, err := expression.EvalBool(foldEvalCtx, expression.CNFExprs{con}, chunk.Row{}) if err != nil { return nil, errors.Trace(err) } @@ -5190,6 +5192,13 @@ func (b *PlanBuilder) BuildDataSourceFromView(ctx context.Context, dbName ast.CI b.hintState = originHintState b.ctx.GetSessionVars().PlannerSelectBlockAsName.Store(originPlannerSelectBlockAsName) }() + // Only relax truncate handling while folding constant predicates in this + // view expansion. Keep the outer statement semantics unchanged. + originIgnoreTruncateErrForViewPredicateFolding := b.ignoreTruncateErrForViewPredicateFolding + b.ignoreTruncateErrForViewPredicateFolding = true + defer func() { + b.ignoreTruncateErrForViewPredicateFolding = originIgnoreTruncateErrForViewPredicateFolding + }() nodeW := resolve.NewNodeWWithCtx(selectNode, b.resolveCtx) selectLogicalPlan, err := b.Build(ctx, nodeW) if err != nil { diff --git a/pkg/planner/core/planbuilder.go b/pkg/planner/core/planbuilder.go index fb53510bc70cc..da6269078af1e 100644 --- a/pkg/planner/core/planbuilder.go +++ b/pkg/planner/core/planbuilder.go @@ -261,6 +261,9 @@ type PlanBuilder struct { partitionedTable []table.PartitionedTable // buildingViewStack is used to check whether there is a recursive view. buildingViewStack set.StringSet + // ignoreTruncateErrForViewPredicateFolding narrows truncate relaxation to + // constant predicate folding while expanding a view. + ignoreTruncateErrForViewPredicateFolding bool // renamingViewName is the name of the view which is being renamed. renamingViewName string // isCreateView indicates whether the query is create view.