Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b64794c
update
daviszhen Jul 31, 2026
68206db
update
daviszhen Jul 31, 2026
d390235
update
daviszhen Jul 31, 2026
3ec76e5
update
daviszhen Jul 31, 2026
6f919cd
update
daviszhen Aug 3, 2026
b8228aa
Merge branch 'main' into 0731-fix-insert-ignore
daviszhen Aug 3, 2026
55d6bcf
update
daviszhen Aug 3, 2026
ca48d92
Merge branch 'main' into 0731-fix-insert-ignore
daviszhen Aug 3, 2026
e9e6272
update
daviszhen Aug 3, 2026
461a9a7
update
daviszhen Aug 3, 2026
3d1a623
update
daviszhen Aug 3, 2026
3570ac3
update
daviszhen Aug 3, 2026
8fc454c
update
daviszhen Aug 3, 2026
b23467f
update
daviszhen Aug 3, 2026
cc8259c
update
daviszhen Aug 3, 2026
7478b88
update
daviszhen Aug 3, 2026
7615549
Merge branch '0731-fix-insert-ignore' of https://github.com/daviszhen…
daviszhen Aug 3, 2026
d1611fe
Merge branch 'main' into 0731-fix-insert-ignore
daviszhen Aug 3, 2026
62a91ec
update
daviszhen Aug 3, 2026
fede392
update
daviszhen Aug 3, 2026
746859c
Merge branch '0731-fix-insert-ignore' of https://github.com/daviszhen…
daviszhen Aug 3, 2026
2f4787c
update
daviszhen Aug 4, 2026
c968c67
update
daviszhen Aug 4, 2026
4497577
Merge branch 'main' into 0731-fix-insert-ignore
daviszhen Aug 4, 2026
39cb6b7
update
daviszhen Aug 4, 2026
acaa808
Merge branch '0731-fix-insert-ignore' of https://github.com/daviszhen…
daviszhen Aug 4, 2026
86ad8fe
Merge branch 'main' into 0731-fix-insert-ignore
daviszhen Aug 4, 2026
fcf5052
update ut
daviszhen Aug 4, 2026
7532437
Merge branch '0731-fix-insert-ignore' of https://github.com/daviszhen…
daviszhen Aug 4, 2026
106af34
update
daviszhen Aug 4, 2026
f1a90e7
update
daviszhen Aug 4, 2026
c716491
Merge branch 'main' into 0731-fix-insert-ignore
daviszhen Aug 4, 2026
50f55a7
Merge branch 'main' into 0731-fix-insert-ignore
mergify[bot] Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 109 additions & 4 deletions pkg/sql/plan/base_binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,10 @@ func (b *baseBinder) baseBindColRef(astExpr *tree.UnresolvedName, depth int32, i
return
}

if isEnumOrSetPlanType(typ) {
// ENUM and SET have distinct storage and display representations. Keep their
// display value by default. Numeric and bitwise expression binders explicitly
// enable raw storage binding so they follow MySQL's numeric semantics.
if !b.bindRawMySQLSpecialType && isEnumOrSetPlanType(typ) {
if err != nil {
errutil.ReportError(b.GetContext(), err)
return
Expand Down Expand Up @@ -714,6 +717,12 @@ func (b *baseBinder) bindRangeCond(astExpr *tree.RangeCond, depth int32, isRoot
}

func (b *baseBinder) bindUnaryExpr(astExpr *tree.UnaryExpr, depth int32, isRoot bool) (*Expr, error) {
if (astExpr.Op == tree.UNARY_PLUS || astExpr.Op == tree.UNARY_MINUS || astExpr.Op == tree.UNARY_TILDE) &&
b.mysqlSpecialTypeInAst(astExpr.Expr) {
return b.bindWithRawMySQLSpecialTypes(func() (*Expr, error) {
return b.bindUnaryExprWithCurrentContext(astExpr, depth)
})
}
if (astExpr.Op == tree.UNARY_MINUS || astExpr.Op == tree.UNARY_PLUS) && b.numericParamType == nil {
return b.bindNumericExprWithDefaultContext(astExpr, depth, b.defaultNumericOuterType())
}
Expand All @@ -735,12 +744,30 @@ func (b *baseBinder) bindUnaryExprWithCurrentContext(astExpr *tree.UnaryExpr, de
}

func (b *baseBinder) bindBinaryExpr(astExpr *tree.BinaryExpr, depth int32, isRoot bool) (*Expr, error) {
if (isNumericBinaryOp(astExpr.Op) || isBitwiseBinaryOp(astExpr.Op)) &&
(b.mysqlSpecialTypeInAst(astExpr.Left) || b.mysqlSpecialTypeInAst(astExpr.Right)) {
return b.bindWithRawMySQLSpecialTypes(func() (*Expr, error) {
if isNumericBinaryOp(astExpr.Op) && b.numericParamType == nil {
return b.bindNumericExprWithDefaultContext(astExpr, depth, b.defaultNumericOuterType())
}
return b.bindBinaryExprWithCurrentContext(astExpr, depth)
})
}
if isNumericBinaryOp(astExpr.Op) && b.numericParamType == nil {
return b.bindNumericExprWithDefaultContext(astExpr, depth, b.defaultNumericOuterType())
}
return b.bindBinaryExprWithCurrentContext(astExpr, depth)
}

func isBitwiseBinaryOp(op tree.BinaryOp) bool {
switch op {
case tree.BIT_XOR, tree.BIT_OR, tree.BIT_AND, tree.LEFT_SHIFT, tree.RIGHT_SHIFT:
return true
default:
return false
}
}

func (b *baseBinder) bindBinaryExprWithCurrentContext(astExpr *tree.BinaryExpr, depth int32) (*Expr, error) {
switch astExpr.Op {
case tree.PLUS:
Expand Down Expand Up @@ -2214,9 +2241,69 @@ func (b *baseBinder) bindComparisonExpr(astExpr *tree.ComparisonExpr, depth int3
if (op == "like" || op == "ilike") && astExpr.Escape != nil {
args = append(args, astExpr.Escape)
}
if b.mysqlSpecialTypeNumericComparison(astExpr.Left, astExpr.Right) {
return b.bindWithRawMySQLSpecialTypes(func() (*Expr, error) {
return b.bindFuncExprImplByAstExpr(op, args, depth)
})
}
return b.bindFuncExprImplByAstExpr(op, args, depth)
}

func (b *baseBinder) bindWithRawMySQLSpecialTypes(bind func() (*Expr, error)) (*Expr, error) {
previous := b.bindRawMySQLSpecialType
b.bindRawMySQLSpecialType = true
defer func() { b.bindRawMySQLSpecialType = previous }()
return bind()
}

func (b *baseBinder) mysqlSpecialTypeNumericComparison(left, right tree.Expr) bool {
return (b.mysqlSpecialTypeAst(left) && mysqlSpecialTypeNumericLiteral(right)) ||
(b.mysqlSpecialTypeAst(right) && mysqlSpecialTypeNumericLiteral(left))
}

func (b *baseBinder) mysqlSpecialTypeAst(expr tree.Expr) bool {
name, ok := unwrapParenExpr(expr).(*tree.UnresolvedName)
if !ok {
return false
}
typ, ok := b.numericColumnType(name)
return ok && isEnumOrSetPlanType(&typ)
}

func (b *baseBinder) mysqlSpecialTypeInAst(expr tree.Expr) bool {
if b.mysqlSpecialTypeAst(expr) {
return true
}
switch value := unwrapParenExpr(expr).(type) {
case *tree.UnaryExpr:
return b.mysqlSpecialTypeInAst(value.Expr)
case *tree.BinaryExpr:
return b.mysqlSpecialTypeInAst(value.Left) || b.mysqlSpecialTypeInAst(value.Right)
}
return false
}

func mysqlSpecialTypeNumericLiteral(expr tree.Expr) bool {
switch value := unwrapParenExpr(expr).(type) {
case *tree.NumVal:
switch value.ValType {
case tree.P_int64, tree.P_uint64, tree.P_float64:
return true
}
case *tree.Tuple:
if len(value.Exprs) == 0 {
return false
}
for _, item := range value.Exprs {
if !mysqlSpecialTypeNumericLiteral(item) {
return false
}
}
return true
}
return false
}

func (b *baseBinder) bindTupleInByAst(leftTuple *tree.Tuple, rightTuple *tree.Tuple, depth int32, isNot bool) (*plan.Expr, error) {
candidates := make([]*plan.Expr, 0, len(rightTuple.Exprs))

Expand Down Expand Up @@ -2539,7 +2626,6 @@ func (b *baseBinder) bindFuncExprImplByAstExpr(name string, astArgs []tree.Expr,
return nil, err
}
}

//promote interval expr rewrite here
if name == "interval" {
if len(astArgs) == 2 {
Expand Down Expand Up @@ -4507,8 +4593,13 @@ func rewriteMySQLSpecialTypeDisplayCast(ctx context.Context, expr *Expr, toType
if toType.Id != int32(types.T_json) {
return expr, false, nil
}
if expr.Typ.Id == int32(types.T_enum) {
return nil, false, moerr.NewInvalidArg(ctx, "operator cast", "[ENUM JSON]")
if isEnumOrSetPlanType(&expr.Typ) {
displayValue, err := makeEnumOrSetDisplayValue(ctx, expr)
if err != nil {
return nil, false, err
}
quoted, err := quoteEnumOrSetDisplayValueAsJSON(ctx, displayValue)
return quoted, err == nil, err
}
if isEnumOrSetDisplayValueExpr(expr) {
quoted, err := quoteEnumOrSetDisplayValueAsJSON(ctx, expr)
Expand All @@ -4517,6 +4608,20 @@ func rewriteMySQLSpecialTypeDisplayCast(ctx context.Context, expr *Expr, toType
return expr, false, nil
}

func makeEnumOrSetDisplayValue(ctx context.Context, expr *Expr) (*Expr, error) {
if expr == nil || !isEnumOrSetPlanType(&expr.Typ) {
return expr, nil
}
indexToValueFun, _, _, err := mysqlSpecialTypeFuncNames(&expr.Typ)
if err != nil {
return nil, err
}
return BindFuncExprImplByPlanExpr(ctx, indexToValueFun, []*Expr{
makePlan2StringConstExprWithType(expr.Typ.Enumvalues),
expr,
})
}

func storedSetBitmapExpr(expr *Expr) (*Expr, bool) {
if !isSetDisplayValueExpr(expr) {
return expr, false
Expand Down
143 changes: 143 additions & 0 deletions pkg/sql/plan/build_expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/matrixorigin/matrixone/pkg/container/types"
"github.com/matrixorigin/matrixone/pkg/pb/plan"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect/mysql"
"github.com/matrixorigin/matrixone/pkg/sql/parsers/tree"
"github.com/matrixorigin/matrixone/pkg/sql/plan/rule"
"github.com/smartystreets/goconvey/convey"
)
Expand Down Expand Up @@ -518,6 +519,122 @@ func TestEnumToJSONQuotesDisplayValueDuringBinding(t *testing.T) {
}
}

func TestEnumAndSetKeepStoredValuesInExpressionContexts(t *testing.T) {
tests := []struct {
name string
typ plan.Type
sql string
wantDisplay bool
}{
{
name: "enum numeric arithmetic",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select n_name + 0 from nation",
},
{
name: "enum numeric unary minus",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select -n_name from nation",
},
{
name: "set bitwise unary complement",
typ: plan.Type{Id: int32(types.T_uint64), Enumvalues: "x,y,z"},
sql: "select ~n_name from nation",
},
{
name: "enum numeric comparison",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select n_name = 1 from nation",
},
{
name: "enum numeric in list",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select n_name in (1, 2) from nation",
},
{
name: "enum mixed string and numeric in list",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select n_name in ('a', 2) from nation",
wantDisplay: true,
},
{
name: "enum string function",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select length(n_name) from nation",
wantDisplay: true,
},
{
name: "enum coalesce",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select coalesce(null, n_name) from nation",
wantDisplay: true,
},
{
name: "set numeric arithmetic",
typ: plan.Type{Id: int32(types.T_uint64), Enumvalues: "x,y,z"},
sql: "select n_name + 0 from nation",
},
{
name: "set bitwise operation",
typ: plan.Type{Id: int32(types.T_uint64), Enumvalues: "x,y,z"},
sql: "select n_name & 1 from nation",
},
{
name: "enum string comparison",
typ: plan.Type{Id: int32(types.T_enum), Enumvalues: "a,b,"},
sql: "select n_name = 'a' from nation",
wantDisplay: true,
},
{
name: "set string comparison",
typ: plan.Type{Id: int32(types.T_uint64), Enumvalues: "x,y,z"},
sql: "select n_name = 'x,z' from nation",
wantDisplay: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
mock := NewMockOptimizer(false)
mock.ctxt.tables["nation"].Cols[1].Typ = tc.typ

pl, err := runOneExprStmt(mock, t, tc.sql)
require.NoError(t, err)
require.Equal(t, tc.wantDisplay, containsEnumOrSetDisplayValue(pl.GetQuery().Nodes[1].ProjectList[0]))
})
}
}

func TestIsBitwiseBinaryOp(t *testing.T) {
for _, op := range []tree.BinaryOp{
tree.BIT_XOR,
tree.BIT_OR,
tree.BIT_AND,
tree.LEFT_SHIFT,
tree.RIGHT_SHIFT,
} {
require.True(t, isBitwiseBinaryOp(op))
}
require.False(t, isBitwiseBinaryOp(tree.PLUS))
}

func containsEnumOrSetDisplayValue(expr *plan.Expr) bool {
if expr == nil {
return false
}
if isEnumOrSetDisplayValueExpr(expr) {
return true
}
if fn := expr.GetF(); fn != nil {
for _, arg := range fn.Args {
if containsEnumOrSetDisplayValue(arg) {
return true
}
}
}
return false
}

func TestEnumDisplayValueToJSONUsesJSONQuoteInPlannerCasts(t *testing.T) {
ctx := NewMockCompilerContext(true).GetContext()
displayExpr := &plan.Expr{
Expand All @@ -544,6 +661,32 @@ func TestEnumDisplayValueToJSONUsesJSONQuoteInPlannerCasts(t *testing.T) {
require.Equal(t, "json_quote", expr.GetF().Func.ObjName)
}

func TestRawMySQLSpecialTypeToJSONUsesDisplayValue(t *testing.T) {
ctx := NewMockCompilerContext(true).GetContext()
for _, typ := range []plan.Type{
{Id: int32(types.T_enum), Enumvalues: "a,b,"},
{Id: int32(types.T_uint64), Enumvalues: "x,y,z"},
} {
raw := &plan.Expr{
Typ: typ,
Expr: &plan.Expr_Col{Col: &plan.ColRef{
RelPos: 1,
ColPos: 2,
Name: "special",
}},
}

got, rewritten, err := rewriteMySQLSpecialTypeDisplayCast(
ctx, raw, plan.Type{Id: int32(types.T_json)},
)
require.NoError(t, err)
require.True(t, rewritten)
require.Equal(t, "json_quote", got.GetF().Func.ObjName)
require.Len(t, got.GetF().Args, 1)
require.True(t, isEnumOrSetDisplayValueExpr(got.GetF().Args[0]))
}
}

func TestSetDisplayValueToJSONUsesJSONQuoteInPlannerCasts(t *testing.T) {
ctx := NewMockCompilerContext(true).GetContext()
displayExpr := &plan.Expr{
Expand Down
35 changes: 33 additions & 2 deletions pkg/sql/plan/function/func_cast.go
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,11 @@ var supportedTypeCast = map[types.T][]types.T{
},

types.T_enum: {
types.T_enum, types.T_uint16, types.T_uint8, types.T_uint32, types.T_uint64, types.T_uint128,
types.T_enum,
types.T_int8, types.T_int16, types.T_int32, types.T_int64,
types.T_uint16, types.T_uint8, types.T_uint32, types.T_uint64, types.T_uint128,
types.T_float32, types.T_float64,
types.T_decimal64, types.T_decimal128, types.T_decimal256,
types.T_char, types.T_varchar, types.T_blob,
types.T_binary, types.T_varbinary, types.T_text,
},
Expand Down Expand Up @@ -1160,6 +1164,9 @@ func castToDecimal256(proc *process.Process, from *vector.Vector, toType types.T
case types.T_uint64:
s := vector.GenerateFunctionFixedTypeParameter[uint64](from)
return unsignedToDecimal256(s, rs, length, selectList)
case types.T_enum:
s := vector.GenerateFunctionFixedTypeParameter[types.Enum](from)
return enumToOthers(proc.Ctx, s, toType, result, length, selectList)
case types.T_float32:
s := vector.GenerateFunctionFixedTypeParameter[float32](from)
return floatToDecimal256(s, rs, length, selectList)
Expand Down Expand Up @@ -2955,9 +2962,33 @@ func enumToOthers(ctx context.Context,
source vector.FunctionParameterWrapper[types.Enum],
toType types.Type, result vector.FunctionResultWrapper, length int, selectList *FunctionSelectList, strictStringWidth ...bool) error {
switch toType.Oid {
case types.T_uint16, types.T_uint8, types.T_uint32, types.T_uint64, types.T_uint128:
case types.T_int8:
return numericToNumeric(ctx, source, vector.MustFunctionResult[int8](result), length, selectList)
case types.T_int16:
return numericToNumeric(ctx, source, vector.MustFunctionResult[int16](result), length, selectList)
case types.T_int32:
return numericToNumeric(ctx, source, vector.MustFunctionResult[int32](result), length, selectList)
case types.T_int64:
return numericToNumeric(ctx, source, vector.MustFunctionResult[int64](result), length, selectList)
case types.T_uint8:
return numericToNumeric(ctx, source, vector.MustFunctionResult[uint8](result), length, selectList)
case types.T_uint16, types.T_uint128:
rs := vector.MustFunctionResult[uint16](result)
return enumToUint16(source, rs, length, selectList)
case types.T_uint32:
return numericToNumeric(ctx, source, vector.MustFunctionResult[uint32](result), length, selectList)
case types.T_uint64:
return numericToNumeric(ctx, source, vector.MustFunctionResult[uint64](result), length, selectList)
case types.T_float32:
return numericToNumeric(ctx, source, vector.MustFunctionResult[float32](result), length, selectList)
case types.T_float64:
return numericToNumeric(ctx, source, vector.MustFunctionResult[float64](result), length, selectList)
case types.T_decimal64:
return unsignedToDecimal64(source, vector.MustFunctionResult[types.Decimal64](result), length, selectList)
case types.T_decimal128:
return unsignedToDecimal128(source, vector.MustFunctionResult[types.Decimal128](result), length, selectList)
case types.T_decimal256:
return unsignedToDecimal256(source, vector.MustFunctionResult[types.Decimal256](result), length, selectList)
case types.T_char, types.T_varchar, types.T_binary, types.T_varbinary, types.T_blob, types.T_text, types.T_datalink:
rs := vector.MustFunctionResult[types.Varlena](result)
return enumToStr(ctx, source, rs, length, selectList, strictStringWidth...)
Expand Down
Loading
Loading