From 5db21e8a5197543e92f2ee9ddd3af6dd8f480e21 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 11:21:40 +0800 Subject: [PATCH 01/14] update --- .../function/func_builtin_leastgreatest.go | 830 +++++++++++++++++- .../func_builtin_leastgreatest_test.go | 397 ++++++++- pkg/sql/plan/function/list_builtIn.go | 54 +- .../function/greatest_least_numeric.result | 60 +- .../cases/function/greatest_least_numeric.sql | 25 + 5 files changed, 1319 insertions(+), 47 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index 481f5f3ce3506..a57c737d669d8 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -16,26 +16,75 @@ package function import ( "bytes" + "fmt" + "strconv" + "time" + "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/matrixorigin/matrixone/pkg/container/nulls" "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/vm/process" ) +type leastGreatestResolution struct { + resultType types.Type + castTypes []types.Type + overloadID int + comparisonMode leastGreatestComparisonMode + temporalItemType types.Type +} + +type leastGreatestComparisonMode uint8 + +type leastGreatestOrdered interface { + ~int8 | ~int16 | ~int32 | ~int64 | + ~uint8 | ~uint16 | ~uint32 | ~uint64 | + ~float32 | ~float64 +} + +const ( + leastGreatestCompareNormal leastGreatestComparisonMode = iota + leastGreatestComparePackedDate +) + +const ( + leastGreatestNormalOverload = iota + leastGreatestTemporalOverload + leastGreatestYearNumericOverload +) + // check input types for least and greatest function. // It requires at least 1 input. NULL arguments (T_any) are allowed and produce // NULL results per MySQL behavior. -// -// When every non-NULL argument already shares the same type, the call succeeds -// directly. When the non-NULL arguments have different numeric types, they are -// promoted to a common numeric type and compared on that type, matching MySQL's -// implicit numeric promotion (issue #25145). Mixing non-numeric types that do -// not already match is still rejected. func leastGreatestCheck(_ []overload, inputs []types.Type) checkResult { - if len(inputs) < 1 { + resolution, ok := resolveLeastGreatestType(inputs) + if !ok { return newCheckResultWithFailure(failedFunctionParametersWrong) } + if len(resolution.castTypes) > 0 { + return newCheckResultWithCast(resolution.overloadID, resolution.castTypes) + } + return newCheckResultWithSuccess(resolution.overloadID) +} + +func leastGreatestReturnType(parameters []types.Type) types.Type { + resolution, ok := resolveLeastGreatestType(parameters) + if ok { + return resolution.resultType + } + for _, p := range parameters { + if p.Oid != types.T_any { + return p + } + } + return types.T_varchar.ToType() +} + +func resolveLeastGreatestType(inputs []types.Type) (leastGreatestResolution, bool) { + if len(inputs) < 1 { + return leastGreatestResolution{}, false + } // Collect the non-NULL (non-T_any) argument types. A NULL literal is typed // T_any and always evaluates to NULL regardless of the resolved type. @@ -47,7 +96,16 @@ func leastGreatestCheck(_ []overload, inputs []types.Type) checkResult { } // All arguments are NULL literals: nothing to compare, evaluate as varchar. if len(nonNull) == 0 { - return newCheckResultWithSuccess(0) + return leastGreatestResolution{resultType: types.T_varchar.ToType(), overloadID: leastGreatestNormalOverload}, true + } + + if allLeastGreatestJSON(nonNull) { + target := types.T_varchar.ToType() + return leastGreatestResolution{ + resultType: target, + castTypes: leastGreatestCastTypes(inputs, target), + overloadID: leastGreatestNormalOverload, + }, true } // Fast path: every non-NULL argument already shares the same type Oid. This @@ -62,21 +120,382 @@ func leastGreatestCheck(_ []overload, inputs []types.Type) checkResult { } } if sameOid { - return newCheckResultWithSuccess(0) + if !leastGreatestExecutorSupportsOid(baseOid) { + return leastGreatestResolution{}, false + } + if target, ok := leastGreatestSameOidAlignedType(nonNull); ok { + return leastGreatestResolution{ + resultType: target, + castTypes: leastGreatestCastTypes(inputs, target), + overloadID: leastGreatestNormalOverload, + }, true + } + return leastGreatestResolution{resultType: nonNull[0], overloadID: leastGreatestNormalOverload}, true } - // Mixed argument types. MySQL promotes numeric arguments to a common type - // and compares on that. If the arguments are not all numeric we cannot - // promote them, so reject the call as before. - target, ok := leastGreatestCommonNumericType(nonNull) - if !ok { - return newCheckResultWithFailure(failedFunctionParametersWrong) + if hasUnsupportedLeastGreatestMixedType(nonNull) { + return leastGreatestResolution{}, false + } + + if hasLeastGreatestJSON(nonNull) && hasLeastGreatestDateBearingTemporal(nonNull) { + target, ok := leastGreatestJSONDateTemporalType(nonNull) + if !ok { + return leastGreatestResolution{}, false + } + return leastGreatestPackedDateResolution(inputs, target), true + } + + if hasLeastGreatestJSON(nonNull) { + target, ok := leastGreatestJSONMixedType(nonNull) + if !ok { + return leastGreatestResolution{}, false + } + return leastGreatestResolution{ + resultType: target, + castTypes: leastGreatestCastTypes(inputs, target), + overloadID: leastGreatestNormalOverload, + }, true + } + + if hasLeastGreatestDateBearingTemporal(nonNull) { + target, ok := leastGreatestDateTemporalMixedType(nonNull) + if !ok { + return leastGreatestResolution{}, false + } + return leastGreatestPackedDateResolution(inputs, target), true + } + + if hasLeastGreatestYear(nonNull) { + if target, ok := leastGreatestCommonNumericOrYearType(nonNull); ok { + return leastGreatestYearNumericResolution(inputs, target), true + } + } + + if target, ok := leastGreatestCommonNumericType(nonNull); ok { + return leastGreatestResolution{ + resultType: target, + castTypes: leastGreatestCastTypes(inputs, target), + overloadID: leastGreatestNormalOverload, + }, true } + + if target, ok := leastGreatestStringMixedType(nonNull); ok { + return leastGreatestResolution{ + resultType: target, + castTypes: leastGreatestCastTypes(inputs, target), + overloadID: leastGreatestNormalOverload, + }, true + } + + return leastGreatestResolution{}, false +} + +func leastGreatestCastTypes(inputs []types.Type, target types.Type) []types.Type { castType := make([]types.Type, len(inputs)) for i := range castType { castType[i] = target } - return newCheckResultWithCast(0, castType) + return castType +} + +func leastGreatestPackedDateResolution(inputs []types.Type, target types.Type) leastGreatestResolution { + castTypes := make([]types.Type, len(inputs)) + for i := range inputs { + if inputs[i].Oid == types.T_any || isLeastGreatestTemporal(inputs[i].Oid) { + castTypes[i] = inputs[i] + } else { + castTypes[i] = target + } + } + return leastGreatestResolution{ + resultType: target, + castTypes: castTypes, + overloadID: leastGreatestTemporalOverload, + comparisonMode: leastGreatestComparePackedDate, + temporalItemType: leastGreatestTemporalItemType(inputs), + } +} + +func leastGreatestYearNumericResolution(inputs []types.Type, target types.Type) leastGreatestResolution { + castTypes := make([]types.Type, len(inputs)) + for i := range inputs { + if inputs[i].Oid == types.T_year { + castTypes[i] = inputs[i] + } else { + castTypes[i] = target + } + } + return leastGreatestResolution{ + resultType: target, + castTypes: castTypes, + overloadID: leastGreatestYearNumericOverload, + } +} + +func allLeastGreatestJSON(inputs []types.Type) bool { + for i := range inputs { + if inputs[i].Oid != types.T_json { + return false + } + } + return true +} + +func hasLeastGreatestJSON(inputs []types.Type) bool { + for i := range inputs { + if inputs[i].Oid == types.T_json { + return true + } + } + return false +} + +func hasLeastGreatestYear(inputs []types.Type) bool { + for i := range inputs { + if inputs[i].Oid == types.T_year { + return true + } + } + return false +} + +func leastGreatestExecutorSupportsOid(oid types.T) bool { + switch oid { + case types.T_bool, types.T_bit, + types.T_int8, types.T_int16, types.T_int32, types.T_int64, + types.T_uint8, types.T_uint16, types.T_uint32, types.T_uint64, + types.T_uuid, types.T_float32, types.T_float64, + types.T_char, types.T_varchar, types.T_blob, types.T_text, types.T_datalink, + types.T_binary, types.T_varbinary, + types.T_array_float32, types.T_array_float64, + types.T_date, types.T_datetime, types.T_time, types.T_timestamp, types.T_year, + types.T_decimal64, types.T_decimal128, types.T_decimal256, + types.T_Rowid: + return true + default: + return false + } +} + +func leastGreatestSameOidAlignedType(inputs []types.Type) (types.Type, bool) { + target := inputs[0] + switch target.Oid { + case types.T_decimal64, types.T_decimal128, types.T_decimal256: + if !leastGreatestMetadataDiffers(inputs) { + return types.Type{}, false + } + if !setSafeDecimalWidthAndScaleFromSource(&target, inputs) { + return types.T_float64.ToType(), true + } + return target, true + case types.T_time, types.T_datetime, types.T_timestamp: + if !leastGreatestMetadataDiffers(inputs) { + return types.Type{}, false + } + setMaxScaleFromSource(&target, inputs) + return target, true + default: + return types.Type{}, false + } +} + +func leastGreatestMetadataDiffers(inputs []types.Type) bool { + for i := 1; i < len(inputs); i++ { + if inputs[i].Width != inputs[0].Width || inputs[i].Scale != inputs[0].Scale { + return true + } + } + return false +} + +func hasUnsupportedLeastGreatestMixedType(inputs []types.Type) bool { + for i := range inputs { + switch inputs[i].Oid { + case types.T_interval, types.T_uuid, types.T_Rowid, + types.T_array_float32, types.T_array_float64, + types.T_geometry, types.T_geometry32, types.T_datalink: + return true + } + } + return false +} + +func hasLeastGreatestDateBearingTemporal(inputs []types.Type) bool { + for i := range inputs { + if isLeastGreatestDateBearingTemporal(inputs[i].Oid) { + return true + } + } + return false +} + +func isLeastGreatestDateBearingTemporal(oid types.T) bool { + return oid == types.T_date || oid == types.T_datetime || oid == types.T_timestamp +} + +func isLeastGreatestTemporal(oid types.T) bool { + return isLeastGreatestDateBearingTemporal(oid) || oid == types.T_time || oid == types.T_year +} + +func leastGreatestTemporalItemType(inputs []types.Type) types.Type { + var best types.Type + bestRank := -1 + for i := range inputs { + rank := leastGreatestTemporalRank(inputs[i].Oid) + if rank > bestRank { + bestRank = rank + best = inputs[i] + } + } + return best +} + +func leastGreatestTemporalRank(oid types.T) int { + switch oid { + case types.T_datetime: + return 5 + case types.T_timestamp: + return 4 + case types.T_date: + return 3 + case types.T_time: + return 2 + case types.T_year: + return 1 + default: + return -1 + } +} + +func leastGreatestJSONDateTemporalType(inputs []types.Type) (types.Type, bool) { + for i := range inputs { + switch inputs[i].Oid { + case types.T_json, types.T_char, types.T_varchar, + types.T_date, types.T_datetime, types.T_timestamp, types.T_time, types.T_year: + default: + if !inputs[i].IsNumeric() { + return types.Type{}, false + } + } + } + return types.T_varchar.ToType(), true +} + +func leastGreatestDateTemporalMixedType(inputs []types.Type) (types.Type, bool) { + allTemporal := true + hasText, hasNonBinary, hasBlob, hasBinary, hasNumeric := false, false, false, false, false + for i := range inputs { + switch inputs[i].Oid { + case types.T_date, types.T_datetime, types.T_timestamp, types.T_time, types.T_year: + case types.T_text: + allTemporal = false + hasText = true + case types.T_char, types.T_varchar: + allTemporal = false + hasNonBinary = true + case types.T_blob: + allTemporal = false + hasBlob = true + case types.T_binary, types.T_varbinary: + allTemporal = false + hasBinary = true + default: + if !inputs[i].IsNumeric() { + return types.Type{}, false + } + allTemporal = false + hasNumeric = true + } + } + switch { + case allTemporal: + return types.T_datetime.ToType(), true + case hasText: + return types.T_text.ToType(), true + case hasNonBinary || hasNumeric: + return types.T_varchar.ToType(), true + case hasBlob: + return types.T_blob.ToType(), true + case hasBinary: + return types.T_varbinary.ToType(), true + default: + return types.Type{}, false + } +} + +func leastGreatestJSONMixedType(inputs []types.Type) (types.Type, bool) { + hasText := false + for i := range inputs { + switch inputs[i].Oid { + case types.T_binary, types.T_varbinary, types.T_blob: + return types.Type{}, false + case types.T_text: + hasText = true + case types.T_json, types.T_char, types.T_varchar, types.T_time, types.T_year: + default: + if !inputs[i].IsNumeric() { + return types.Type{}, false + } + } + } + if hasText { + return types.T_text.ToType(), true + } + return types.T_varchar.ToType(), true +} + +func leastGreatestCommonNumericOrYearType(inputs []types.Type) (types.Type, bool) { + numericInputs := make([]types.Type, len(inputs)) + for i := range inputs { + if inputs[i].Oid == types.T_year { + numericInputs[i] = types.T_uint16.ToType() + continue + } + if !inputs[i].IsNumeric() { + return types.Type{}, false + } + numericInputs[i] = inputs[i] + } + return leastGreatestCommonNumericType(numericInputs) +} + +func leastGreatestStringMixedType(inputs []types.Type) (types.Type, bool) { + hasText, hasNonBinary, hasBlob, hasBinary, hasTemporal, hasNumeric := false, false, false, false, false, false + for i := range inputs { + switch inputs[i].Oid { + case types.T_text: + hasText = true + case types.T_char, types.T_varchar: + hasNonBinary = true + case types.T_blob: + hasBlob = true + case types.T_binary, types.T_varbinary: + hasBinary = true + case types.T_date, types.T_datetime, types.T_timestamp, types.T_time, types.T_year: + hasTemporal = true + default: + if !inputs[i].IsNumeric() { + return types.Type{}, false + } + hasNumeric = true + } + } + switch { + case hasText: + return types.T_text.ToType(), true + case hasNonBinary: + return types.T_varchar.ToType(), true + case hasTemporal && hasNumeric: + return types.T_varchar.ToType(), true + case hasBlob: + return types.T_blob.ToType(), true + case hasBinary: + return types.T_varbinary.ToType(), true + case hasTemporal: + return types.T_varchar.ToType(), true + default: + return types.Type{}, false + } } // leastGreatestCommonNumericType derives the common type used to compare a set @@ -209,8 +628,7 @@ func leastGreatestFnFixed[T types.FixedSizeTExceptStrType]( if selectList != nil { if selectList.IgnoreAllRow() { - nulls.AddRange(rsNull, 0, uint64(length)) - return nil + return leastGreatestSetNullResult(result, length) } if !selectList.ShouldEvalAllRow() { rsAnyNull = true @@ -225,8 +643,7 @@ func leastGreatestFnFixed[T types.FixedSizeTExceptStrType]( for np, pv := range parameters { p := vector.GenerateFunctionFixedTypeParameter[T](pv) if pv.IsConstNull() { - nulls.AddRange(rsNull, 0, uint64(length)) - return nil + return leastGreatestSetNullResult(result, length) } if p.WithAnyNullValue() || rsAnyNull { nulls.Or(rsNull, pv.GetNulls(), rsNull) @@ -263,8 +680,7 @@ func leastGreatestFnVarlen( if selectList != nil { if selectList.IgnoreAllRow() { - nulls.AddRange(rsNull, 0, uint64(length)) - return nil + return leastGreatestSetNullResult(result, length) } if !selectList.ShouldEvalAllRow() { for i := range selectList.SelectList { @@ -277,8 +693,7 @@ func leastGreatestFnVarlen( for _, pv := range parameters { if pv.IsConstNull() { - nulls.AddRange(rsNull, 0, uint64(length)) - return nil + return leastGreatestSetNullResult(result, length) } } @@ -314,6 +729,333 @@ func leastGreatestFnVarlen( return nil } +func leastGreatestYearNumericFn( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList, + compareFn func(int) bool) error { + target := *result.GetResultVector().GetType() + switch target.Oid { + case types.T_int8: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (int8, error) { + return int8(y), nil + }, func(v1, v2 int8) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_int16: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (int16, error) { + return int16(y), nil + }, func(v1, v2 int16) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_int32: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (int32, error) { + return int32(y), nil + }, func(v1, v2 int32) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_int64: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (int64, error) { + return y, nil + }, func(v1, v2 int64) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_uint8: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (uint8, error) { + return uint8(y), nil + }, func(v1, v2 uint8) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_uint16: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (uint16, error) { + return uint16(y), nil + }, func(v1, v2 uint16) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_uint32: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (uint32, error) { + return uint32(y), nil + }, func(v1, v2 uint32) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_uint64: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (uint64, error) { + return uint64(y), nil + }, func(v1, v2 uint64) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_float64: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (float64, error) { + return float64(y), nil + }, func(v1, v2 float64) bool { return compareFn(compareOrdered(v1, v2)) }) + case types.T_decimal64: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (types.Decimal64, error) { + return types.ParseDecimal64(strconv.FormatInt(y, 10), target.Width, target.Scale) + }, func(v1, v2 types.Decimal64) bool { return compareFn(v1.Compare(v2)) }) + case types.T_decimal128: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (types.Decimal128, error) { + return types.ParseDecimal128(strconv.FormatInt(y, 10), target.Width, target.Scale) + }, func(v1, v2 types.Decimal128) bool { return compareFn(v1.Compare(v2)) }) + case types.T_decimal256: + return leastGreatestYearNumericFnFixed(parameters, result, proc, length, selectList, func(y int64) (types.Decimal256, error) { + return types.ParseDecimal256(strconv.FormatInt(y, 10), target.Width, target.Scale) + }, func(v1, v2 types.Decimal256) bool { return compareFn(v1.Compare(v2)) }) + default: + return moerr.NewInternalErrorNoCtxf("unsupported YEAR numeric comparison result type %s", target.Oid.String()) + } +} + +func compareOrdered[T leastGreatestOrdered](v1, v2 T) int { + if v1 < v2 { + return -1 + } + if v1 > v2 { + return 1 + } + return 0 +} + +func leastGreatestYearNumericFnFixed[T types.FixedSizeTExceptStrType]( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList, + yearToTarget func(int64) (T, error), + compareFn func(v1, v2 T) bool) error { + rs := vector.MustFunctionResult[T](result) + rsVec := rs.GetResultVector() + rss := vector.MustFixedColWithTypeCheck[T](rsVec) + rsNull := rsVec.GetNulls() + + if selectList != nil { + if selectList.IgnoreAllRow() { + return leastGreatestSetNullResult(result, length) + } + if !selectList.ShouldEvalAllRow() { + for i := range selectList.SelectList { + if selectList.Contains(uint64(i)) { + rsNull.Add(uint64(i)) + } + } + } + } + + for _, pv := range parameters { + if pv.IsConstNull() { + return leastGreatestSetNullResult(result, length) + } + } + + yearParams := make([]vector.FunctionParameterWrapper[types.MoYear], len(parameters)) + numericParams := make([]vector.FunctionParameterWrapper[T], len(parameters)) + for i, pv := range parameters { + if pv.GetType().Oid == types.T_year { + yearParams[i] = vector.GenerateFunctionFixedTypeParameter[types.MoYear](pv) + } else { + numericParams[i] = vector.GenerateFunctionFixedTypeParameter[T](pv) + } + } + + for i := uint64(0); i < uint64(length); i++ { + if rsNull.Contains(i) { + continue + } + + var winner T + for j, pv := range parameters { + if pv.IsNull(i) { + rsNull.Add(i) + break + } + + var value T + if pv.GetType().Oid == types.T_year { + year, _ := yearParams[j].GetValue(i) + var err error + value, err = yearToTarget(year.ToInt64()) + if err != nil { + return err + } + } else { + value, _ = numericParams[j].GetValue(i) + } + + if j == 0 || compareFn(value, winner) { + winner = value + } + } + if !rsNull.Contains(i) { + rss[i] = winner + } + } + return nil +} + +func leastGreatestFnPackedDate( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList, + compareFn func(v1, v2 types.Datetime) bool) error { + resolution, ok := resolveLeastGreatestType(vectorTypes(parameters)) + if !ok || resolution.comparisonMode != leastGreatestComparePackedDate { + return moerr.NewInternalErrorNoCtx("unsupported temporal comparison resolution") + } + + rsVec := result.GetResultVector() + rsNull := rsVec.GetNulls() + if selectList != nil { + if selectList.IgnoreAllRow() { + return leastGreatestSetNullResult(result, length) + } + if !selectList.ShouldEvalAllRow() { + for i := range selectList.SelectList { + if selectList.Contains(uint64(i)) { + rsNull.Add(uint64(i)) + } + } + } + } + + for _, pv := range parameters { + if pv.IsConstNull() { + return leastGreatestSetNullResult(result, length) + } + } + + loc := time.Local + if proc != nil && proc.GetSessionInfo() != nil { + loc = proc.GetSessionInfo().TimeZone + } + + for i := uint64(0); i < uint64(length); i++ { + if selectList != nil && selectList.ShouldEvalAllRow() && selectList.Contains(i) { + if err := leastGreatestAppendPackedDateResult(result, resolution, types.Datetime(0), loc, true); err != nil { + return err + } + continue + } + if rsNull.Contains(i) { + if err := leastGreatestAppendPackedDateResult(result, resolution, types.Datetime(0), loc, true); err != nil { + return err + } + continue + } + + var winner types.Datetime + for j, pv := range parameters { + if pv.IsNull(i) { + if err := leastGreatestAppendPackedDateResult(result, resolution, types.Datetime(0), loc, true); err != nil { + return err + } + goto nextRow + } + v, err := leastGreatestDatetimeValue(pv, i, loc) + if err != nil { + return err + } + if j == 0 || compareFn(v, winner) { + winner = v + } + } + if err := leastGreatestAppendPackedDateResult(result, resolution, winner, loc, false); err != nil { + return err + } + nextRow: + } + return nil +} + +func vectorTypes(parameters []*vector.Vector) []types.Type { + ts := make([]types.Type, len(parameters)) + for i := range parameters { + ts[i] = *parameters[i].GetType() + } + return ts +} + +func leastGreatestSetNullResult(result vector.FunctionResultWrapper, length int) error { + if result.GetResultVector().GetType().IsVarlen() { + rs := vector.MustFunctionResult[types.Varlena](result) + rs.SetNullResult(uint64(length)) + return nil + } + nulls.AddRange(result.GetResultVector().GetNulls(), 0, uint64(length)) + return nil +} + +func leastGreatestDatetimeValue(v *vector.Vector, row uint64, loc *time.Location) (types.Datetime, error) { + switch v.GetType().Oid { + case types.T_date: + p := vector.GenerateFunctionFixedTypeParameter[types.Date](v) + val, _ := p.GetValue(row) + return val.ToDatetime(), nil + case types.T_datetime: + p := vector.GenerateFunctionFixedTypeParameter[types.Datetime](v) + val, _ := p.GetValue(row) + return val, nil + case types.T_timestamp: + p := vector.GenerateFunctionFixedTypeParameter[types.Timestamp](v) + val, _ := p.GetValue(row) + return val.ToDatetime(loc), nil + case types.T_time: + p := vector.GenerateFunctionFixedTypeParameter[types.Time](v) + val, _ := p.GetValue(row) + return val.ToDatetime(v.GetType().Scale), nil + case types.T_year: + p := vector.GenerateFunctionFixedTypeParameter[types.MoYear](v) + val, _ := p.GetValue(row) + return types.DatetimeFromClock(int32(val.ToInt64()), 1, 1, 0, 0, 0, 0), nil + case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: + return leastGreatestParseDatetimeBytes(v.GetBytesAt(int(row))) + default: + return types.Datetime(0), moerr.NewInternalErrorNoCtxf("unsupported temporal comparison type %s", v.GetType().Oid.String()) + } +} + +func leastGreatestParseDatetimeBytes(v []byte) (types.Datetime, error) { + s := string(v) + if dt, err := types.ParseDatetime(s, 6); err == nil { + return dt, nil + } + if d, err := types.ParseDateCast(s); err == nil { + return d.ToDatetime(), nil + } + if t, err := types.ParseTime(s, 6); err == nil { + return t.ToDatetime(6), nil + } + _, err := types.ParseDatetime(s, 6) + return types.Datetime(0), err +} + +func leastGreatestAppendPackedDateResult( + result vector.FunctionResultWrapper, + resolution leastGreatestResolution, + value types.Datetime, + loc *time.Location, + isNull bool) error { + switch resolution.resultType.Oid { + case types.T_datetime: + rs := vector.MustFunctionResult[types.Datetime](result) + return rs.Append(value, isNull) + case types.T_date: + rs := vector.MustFunctionResult[types.Date](result) + return rs.Append(value.ToDate(), isNull) + case types.T_timestamp: + rs := vector.MustFunctionResult[types.Timestamp](result) + return rs.Append(value.ToTimestamp(loc), isNull) + case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: + rs := vector.MustFunctionResult[types.Varlena](result) + return rs.AppendBytes([]byte(leastGreatestFormatPackedDate(value, resolution.temporalItemType, loc)), isNull) + default: + return moerr.NewInternalErrorNoCtxf("unsupported temporal comparison result type %s", resolution.resultType.Oid.String()) + } +} + +func leastGreatestFormatPackedDate(value types.Datetime, temporalItemType types.Type, loc *time.Location) string { + switch temporalItemType.Oid { + case types.T_date: + return value.ToDate().String() + case types.T_time: + return value.ToTime(temporalItemType.Scale).String2(temporalItemType.Scale) + case types.T_timestamp: + return value.ToTimestamp(loc).String2(loc, temporalItemType.Scale) + case types.T_year: + year, _, _, _ := value.ToDate().Calendar(true) + return fmt.Sprintf("%04d", year) + default: + return value.String2(temporalItemType.Scale) + } +} + // leastGreatestParamType finds the first non-T_any parameter type. // If all parameters are T_any (all NULL constants), returns T_varchar. func leastGreatestParamType(parameters []*vector.Vector) types.Type { @@ -619,6 +1361,26 @@ func leastFn(parameters []*vector.Vector, panic("unreached code") } +func leastTemporalFn(parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList) error { + return leastGreatestFnPackedDate(parameters, result, proc, length, selectList, func(v1, v2 types.Datetime) bool { + return v1 < v2 + }) +} + +func leastYearNumericFn(parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList) error { + return leastGreatestYearNumericFn(parameters, result, proc, length, selectList, func(comparison int) bool { + return comparison < 0 + }) +} + func greatestFn(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, @@ -911,3 +1673,23 @@ func greatestFn(parameters []*vector.Vector, } panic("unreached code") } + +func greatestTemporalFn(parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList) error { + return leastGreatestFnPackedDate(parameters, result, proc, length, selectList, func(v1, v2 types.Datetime) bool { + return v1 > v2 + }) +} + +func greatestYearNumericFn(parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList) error { + return leastGreatestYearNumericFn(parameters, result, proc, length, selectList, func(comparison int) bool { + return comparison > 0 + }) +} diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 168dc6c1a65fb..4a39ddba7b0eb 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -15,6 +15,7 @@ package function import ( + "context" "fmt" "testing" @@ -552,13 +553,76 @@ func TestLeastGreatestCheck(t *testing.T) { target: types.T_decimal128, }, { - name: "numeric + varchar rejected", - inputs: []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, + name: "numeric + varchar -> varchar", + inputs: []types.Type{types.T_int64.ToType(), types.T_varchar.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_varchar, + }, + { + name: "varchar + int -> varchar", + inputs: []types.Type{types.T_varchar.ToType(), types.T_int64.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_varchar, + }, + { + name: "varchar + bigint + double -> varchar", + inputs: []types.Type{types.T_varchar.ToType(), types.T_int64.ToType(), types.T_float64.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_varchar, + }, + { + name: "text + numeric -> text", + inputs: []types.Type{types.T_text.ToType(), types.T_int64.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_text, + }, + { + name: "varchar + varbinary -> varchar", + inputs: []types.Type{types.T_varchar.ToType(), types.T_varbinary.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_varchar, + }, + { + name: "blob + numeric -> blob", + inputs: []types.Type{types.T_blob.ToType(), types.T_int64.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_blob, + }, + { + name: "binary + numeric -> varbinary", + inputs: []types.Type{types.T_binary.ToType(), types.T_int64.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_varbinary, + }, + { + name: "json + varchar -> varchar", + inputs: []types.Type{types.T_json.ToType(), types.T_varchar.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_varchar, + }, + { + name: "json + text -> text", + inputs: []types.Type{types.T_json.ToType(), types.T_text.ToType()}, + wantOk: true, + wantCast: true, + target: types.T_text, + }, + { + name: "json + blob rejected", + inputs: []types.Type{types.T_json.ToType(), types.T_blob.ToType()}, wantOk: false, }, { - name: "varchar + int (first non-numeric) rejected", - inputs: []types.Type{types.T_varchar.ToType(), types.T_int64.ToType()}, + name: "json + binary rejected", + inputs: []types.Type{types.T_json.ToType(), types.T_binary.ToType()}, wantOk: false, }, } @@ -585,6 +649,331 @@ func TestLeastGreatestCheck(t *testing.T) { } } +func TestLeastGreatestFunctionResolution(t *testing.T) { + ctx := context.Background() + cases := []struct { + name string + args []types.Type + wantReturn types.T + wantCast bool + wantTargets []types.T + }{ + { + name: "greatest varchar bigint returns varchar", + args: []types.Type{types.T_varchar.ToType(), types.T_int64.ToType()}, + wantReturn: types.T_varchar, + wantCast: true, + wantTargets: []types.T{types.T_varchar, types.T_varchar}, + }, + { + name: "least text bigint returns text", + args: []types.Type{types.T_text.ToType(), types.T_int64.ToType()}, + wantReturn: types.T_text, + wantCast: true, + wantTargets: []types.T{types.T_text, types.T_text}, + }, + { + name: "pure json returns varchar", + args: []types.Type{types.T_json.ToType(), types.T_json.ToType()}, + wantReturn: types.T_varchar, + wantCast: true, + wantTargets: []types.T{types.T_varchar, types.T_varchar}, + }, + { + name: "binary bigint returns varbinary", + args: []types.Type{types.T_binary.ToType(), types.T_int64.ToType()}, + wantReturn: types.T_varbinary, + wantCast: true, + wantTargets: []types.T{types.T_varbinary, types.T_varbinary}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + for _, fnName := range []string{"greatest", "least"} { + fn, err := GetFunctionByName(ctx, fnName, c.args) + require.NoError(t, err, fnName) + require.Equal(t, c.wantReturn, fn.GetReturnType().Oid, fnName) + targets, shouldCast := fn.ShouldDoImplicitTypeCast() + require.Equal(t, c.wantCast, shouldCast, fnName) + require.Len(t, targets, len(c.wantTargets), fnName) + for i := range c.wantTargets { + require.Equal(t, c.wantTargets[i], targets[i].Oid, "%s target %d", fnName, i) + } + } + }) + } +} + +func TestLeastGreatestTemporalResolution(t *testing.T) { + cases := []struct { + name string + inputs []types.Type + wantOK bool + wantReturn types.T + wantOverload int + wantMode leastGreatestComparisonMode + wantTargets []types.T + }{ + { + name: "date varchar uses packed date and returns varchar", + inputs: []types.Type{types.T_date.ToType(), types.T_varchar.ToType()}, + wantOK: true, + wantReturn: types.T_varchar, + wantOverload: leastGreatestTemporalOverload, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_date, types.T_varchar}, + }, + { + name: "date datetime uses packed date and returns datetime", + inputs: []types.Type{types.T_date.ToType(), types.T_datetime.ToType()}, + wantOK: true, + wantReturn: types.T_datetime, + wantOverload: leastGreatestTemporalOverload, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_date, types.T_datetime}, + }, + { + name: "json date bigint casts non temporal peers to varchar", + inputs: []types.Type{types.T_json.ToType(), types.T_date.ToType(), types.T_int64.ToType()}, + wantOK: true, + wantReturn: types.T_varchar, + wantOverload: leastGreatestTemporalOverload, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_varchar, types.T_date, types.T_varchar}, + }, + { + name: "year date bigint keeps temporal peers", + inputs: []types.Type{types.T_year.ToType(), types.T_date.ToType(), types.T_int64.ToType()}, + wantOK: true, + wantReturn: types.T_varchar, + wantOverload: leastGreatestTemporalOverload, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_year, types.T_date, types.T_varchar}, + }, + { + name: "json date text rejected", + inputs: []types.Type{types.T_json.ToType(), types.T_date.ToType(), types.T_text.ToType()}, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + resolution, ok := resolveLeastGreatestType(c.inputs) + require.Equal(t, c.wantOK, ok) + if !ok { + return + } + require.Equal(t, c.wantReturn, resolution.resultType.Oid) + require.Equal(t, c.wantOverload, resolution.overloadID) + require.Equal(t, c.wantMode, resolution.comparisonMode) + require.Len(t, resolution.castTypes, len(c.wantTargets)) + for i := range c.wantTargets { + require.Equal(t, c.wantTargets[i], resolution.castTypes[i].Oid, "target %d", i) + } + }) + } +} + +func TestLeastGreatestHighPriorityResolution(t *testing.T) { + decimalScale1 := types.New(types.T_decimal64, 10, 1) + decimalScale2 := types.New(types.T_decimal64, 12, 2) + + t.Run("year numeric keeps year for specialized executor", func(t *testing.T) { + resolution, ok := resolveLeastGreatestType([]types.Type{ + types.T_year.ToType(), + types.T_decimal128.ToType(), + }) + require.True(t, ok) + require.Equal(t, types.T_decimal128, resolution.resultType.Oid) + require.Equal(t, 2, resolution.overloadID) + require.Equal(t, []types.T{types.T_year, types.T_decimal128}, []types.T{ + resolution.castTypes[0].Oid, + resolution.castTypes[1].Oid, + }) + }) + + t.Run("same oid decimal aligns metadata", func(t *testing.T) { + resolution, ok := resolveLeastGreatestType([]types.Type{decimalScale1, decimalScale2}) + require.True(t, ok) + require.Equal(t, types.T_decimal64, resolution.resultType.Oid) + require.Equal(t, int32(12), resolution.resultType.Width) + require.Equal(t, int32(2), resolution.resultType.Scale) + require.Len(t, resolution.castTypes, 2) + for _, target := range resolution.castTypes { + require.Equal(t, resolution.resultType, target) + } + }) + + t.Run("same oid decimal256 precision overflow falls back to float64", func(t *testing.T) { + decimalScale75 := types.New(types.T_decimal256, 76, 75) + decimalScale0 := types.New(types.T_decimal256, 76, 0) + resolution, ok := resolveLeastGreatestType([]types.Type{decimalScale75, decimalScale0}) + require.True(t, ok) + require.Equal(t, types.T_float64, resolution.resultType.Oid) + require.Equal(t, []types.T{types.T_float64, types.T_float64}, []types.T{ + resolution.castTypes[0].Oid, + resolution.castTypes[1].Oid, + }) + + for _, name := range []string{"greatest", "least"} { + fn, err := GetFunctionByName(context.Background(), name, []types.Type{decimalScale75, decimalScale0}) + require.NoError(t, err) + require.Equal(t, types.T_float64, fn.GetReturnType().Oid) + targets, shouldCast := fn.ShouldDoImplicitTypeCast() + require.True(t, shouldCast) + require.Equal(t, []types.T{types.T_float64, types.T_float64}, []types.T{targets[0].Oid, targets[1].Oid}) + } + }) + + for _, oid := range []types.T{types.T_time, types.T_datetime, types.T_timestamp} { + t.Run(oid.String()+" same oid aligns scale", func(t *testing.T) { + scale1 := oid.ToType() + scale1.Scale = 1 + scale2 := oid.ToType() + scale2.Scale = 4 + + resolution, ok := resolveLeastGreatestType([]types.Type{scale1, scale2}) + require.True(t, ok) + require.Equal(t, oid, resolution.resultType.Oid) + require.Equal(t, int32(4), resolution.resultType.Scale) + require.Len(t, resolution.castTypes, 2) + for _, target := range resolution.castTypes { + require.Equal(t, resolution.resultType, target) + } + }) + } + + t.Run("unsupported same oid is rejected before executor", func(t *testing.T) { + intervalType := types.Type{Oid: types.T_interval} + _, ok := resolveLeastGreatestType([]types.Type{intervalType, intervalType}) + require.False(t, ok) + }) +} + +func TestLeastGreatestPackedDateMaterializesNullRows(t *testing.T) { + proc := testutil.NewProcess(t) + d1, err := types.ParseDateCast("2020-01-01") + require.NoError(t, err) + dateTyp := types.T_date.ToType() + varcharTyp := types.T_varchar.ToType() + + t.Run("ignore all rows", func(t *testing.T) { + fcTC := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(dateTyp, []types.Date{d1, d1, d1}, nil), + NewFunctionTestInput(varcharTyp, []string{"2020-01-01", "2020-01-01", "2020-01-01"}, nil), + }, + NewFunctionTestResult(varcharTyp, false, nil, nil), + greatestTemporalFn) + require.NoError(t, fcTC.result.PreExtendAndReset(fcTC.fnLength)) + require.NoError(t, fcTC.fn(fcTC.parameters, fcTC.result, fcTC.proc, fcTC.fnLength, &FunctionSelectList{AllNull: true})) + + resultVec := fcTC.result.GetResultVector() + require.Equal(t, fcTC.fnLength, resultVec.Length()) + for i := 0; i < fcTC.fnLength; i++ { + require.True(t, resultVec.IsNull(uint64(i))) + } + }) + + t.Run("constant null argument", func(t *testing.T) { + fcTC := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(dateTyp, []types.Date{d1, d1, d1}, nil), + NewFunctionTestConstInput(varcharTyp, []string{"2020-01-01"}, []bool{true}), + }, + NewFunctionTestResult(varcharTyp, false, nil, nil), + greatestTemporalFn) + require.NoError(t, fcTC.result.PreExtendAndReset(fcTC.fnLength)) + require.NoError(t, fcTC.fn(fcTC.parameters, fcTC.result, fcTC.proc, fcTC.fnLength, nil)) + + resultVec := fcTC.result.GetResultVector() + require.Equal(t, fcTC.fnLength, resultVec.Length()) + for i := 0; i < fcTC.fnLength; i++ { + require.True(t, resultVec.IsNull(uint64(i))) + } + }) +} + +func TestLeastGreatestYearNumericExecutor(t *testing.T) { + proc := testutil.NewProcess(t) + decimalType := types.New(types.T_decimal128, 10, 1) + parseDecimal := func(values ...string) []types.Decimal128 { + result := make([]types.Decimal128, len(values)) + for i, value := range values { + parsed, err := types.ParseDecimal128(value, decimalType.Width, decimalType.Scale) + require.NoError(t, err) + result[i] = parsed + } + return result + } + + inputs := []FunctionTestInput{ + NewFunctionTestInput(types.T_year.ToType(), []types.MoYear{2020, 2022}, nil), + NewFunctionTestInput(decimalType, parseDecimal("2019.5", "2022.5"), nil), + } + + tcGreatest := NewFunctionTestCase(proc, + inputs, + NewFunctionTestResult(decimalType, false, parseDecimal("2020.0", "2022.5"), nil), + greatestYearNumericFn) + ok, info := tcGreatest.Run() + require.True(t, ok, info) + + tcLeast := NewFunctionTestCase(proc, + inputs, + NewFunctionTestResult(decimalType, false, parseDecimal("2019.5", "2022.0"), nil), + leastYearNumericFn) + ok, info = tcLeast.Run() + require.True(t, ok, info) +} + +func TestLeastGreatestYearNumericFunctionResolution(t *testing.T) { + for _, name := range []string{"greatest", "least"} { + fn, err := GetFunctionByName(context.Background(), name, []types.Type{ + types.T_year.ToType(), + types.New(types.T_decimal128, 10, 1), + }) + require.NoError(t, err) + require.Equal(t, int32(leastGreatestYearNumericOverload), fn.overloadId) + require.Equal(t, types.T_decimal128, fn.GetReturnType().Oid) + + targets, shouldCast := fn.ShouldDoImplicitTypeCast() + require.True(t, shouldCast) + require.Equal(t, []types.T{types.T_year, types.T_decimal128}, []types.T{targets[0].Oid, targets[1].Oid}) + } +} + +func TestLeastGreatestTemporalExecutor(t *testing.T) { + proc := testutil.NewProcess(t) + d1, err := types.ParseDateCast("2020-01-01") + require.NoError(t, err) + d2, err := types.ParseDateCast("2020-01-03") + require.NoError(t, err) + dateTyp := types.T_date.ToType() + varcharTyp := types.T_varchar.ToType() + + tcGreatest := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(dateTyp, []types.Date{d1, d2}, nil), + NewFunctionTestInput(varcharTyp, []string{"2020-01-02", "2020-01-01"}, nil), + }, + NewFunctionTestResult(varcharTyp, false, []string{"2020-01-02", "2020-01-03"}, nil), + greatestTemporalFn) + ok, info := tcGreatest.Run() + require.True(t, ok, info) + + tcLeast := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(dateTyp, []types.Date{d1, d2}, nil), + NewFunctionTestInput(varcharTyp, []string{"2020-01-02", "2020-01-01"}, nil), + }, + NewFunctionTestResult(varcharTyp, false, []string{"2020-01-01", "2020-01-01"}, nil), + leastTemporalFn) + ok, info = tcLeast.Run() + require.True(t, ok, info) +} + // TestLeastGreatestWidthHelpers covers every branch of the integer-width → // type helpers used by the LEAST/GREATEST promotion logic. func TestLeastGreatestWidthHelpers(t *testing.T) { diff --git a/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index e896bbc6eec7a..baf30a69ddbff 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -736,19 +736,30 @@ var supportedStringBuiltIns = []FuncNew{ { overloadId: 0, retType: func(parameters []types.Type) types.Type { - // return the first non-T_any type (skip NULL arguments) - // if all are T_any, return T_varchar as MySQL does for NULL literals - for _, p := range parameters { - if p.Oid != types.T_any { - return p - } - } - return types.T_varchar.ToType() + return leastGreatestReturnType(parameters) }, newOp: func() executeLogicOfOverload { return greatestFn }, }, + { + overloadId: 1, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, + newOp: func() executeLogicOfOverload { + return greatestTemporalFn + }, + }, + { + overloadId: 2, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, + newOp: func() executeLogicOfOverload { + return greatestYearNumericFn + }, + }, }, }, @@ -2070,19 +2081,30 @@ var supportedStringBuiltIns = []FuncNew{ { overloadId: 0, retType: func(parameters []types.Type) types.Type { - // return the first non-T_any type (skip NULL arguments) - // if all are T_any, return T_varchar as MySQL does for NULL literals - for _, p := range parameters { - if p.Oid != types.T_any { - return p - } - } - return types.T_varchar.ToType() + return leastGreatestReturnType(parameters) }, newOp: func() executeLogicOfOverload { return leastFn }, }, + { + overloadId: 1, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, + newOp: func() executeLogicOfOverload { + return leastTemporalFn + }, + }, + { + overloadId: 2, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, + newOp: func() executeLogicOfOverload { + return leastYearNumericFn + }, + }, }, }, diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 3c88f05b2d9bc..22421dd6b28f8 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -85,16 +85,16 @@ select greatest(u1, u2, u3) as g3u, least(u1, u2, u3) as l3u from uns order by u 9000000000 ¦ 100 𝄀 200 ¦ 1 select greatest(cast(-3 as bigint), cast(9000000000000000000 as bigint unsigned)) as g_su; -➤ g_su[3,21,0] 𝄀 +➤ g_su[3,20,0] 𝄀 9000000000000000000 select least(cast(-3 as bigint), cast(9000000000000000000 as bigint unsigned)) as l_su; -➤ l_su[3,21,0] 𝄀 +➤ l_su[3,20,0] 𝄀 -3 drop table if exists bt; create table bt(b bit(16), i int); insert into bt values (10, 3), (20, 25), (7, 7); select greatest(b, i) as g_bi, least(b, i) as l_bi from bt order by i; -➤ g_bi[3,21,0] ¦ l_bi[3,21,0] 𝄀 +➤ g_bi[3,20,0] ¦ l_bi[3,20,0] 𝄀 10 ¦ 3 𝄀 7 ¦ 7 𝄀 25 ¦ 20 @@ -110,6 +110,60 @@ select greatest(cast(1234567890123456789012345678901234567890.12 as decimal(60,2 select least(cast(1234567890123456789012345678901234567890.12 as decimal(60,2)), cast(2 as bigint)) as l_d256; ➤ l_d256[3,60,2] 𝄀 2.00 +select greatest(cast(0.9 as decimal(65,65)), cast(1 as decimal(65,0))) as g_d256_scale_overflow; +➤ g_d256_scale_overflow[8,54,0] 𝄀 +1.0 +select least(cast(0.9 as decimal(65,65)), cast(1 as decimal(65,0))) as l_d256_scale_overflow; +➤ l_d256_scale_overflow[8,54,0] 𝄀 +0.8999999999999999 +select greatest('10', 2) as greatest_str_num; +➤ greatest_str_num[12,-1,0] 𝄀 +2 +select least('10', 2) as least_str_num; +➤ least_str_num[12,-1,0] 𝄀 +10 +select greatest(2, '10') as greatest_num_str; +➤ greatest_num_str[12,-1,0] 𝄀 +2 +select least(2, '10') as least_num_str; +➤ least_num_str[12,-1,0] 𝄀 +10 +select greatest('10', 2, 11.0) as greatest_three_mixed; +➤ greatest_three_mixed[12,-1,0] 𝄀 +2 +select least('10', 2, 11.0) as least_three_mixed; +➤ least_three_mixed[12,-1,0] 𝄀 +10 +select greatest('B', binary 'A') as greatest_nonbinary_binary; +➤ greatest_nonbinary_binary[12,-1,0] 𝄀 +B +select least('B', binary 'A') as least_nonbinary_binary; +➤ least_nonbinary_binary[12,-1,0] 𝄀 +A +select greatest(cast('10' as text), 2) as greatest_text_num; +➤ greatest_text_num[12,0,0] 𝄀 +2 +select least(cast('10' as text), 2) as least_text_num; +➤ least_text_num[12,0,0] 𝄀 +10 +select hex(greatest(cast('61' as blob), 2)) as greatest_blob_num; +➤ greatest_blob_num[12,-1,0] 𝄀 +3631 +select hex(least(cast('61' as blob), 2)) as least_blob_num; +➤ least_blob_num[12,-1,0] 𝄀 +32 +select hex(greatest(cast('61' as binary), 2)) as greatest_binary_num; +➤ greatest_binary_num[12,-1,0] 𝄀 +3631 +select hex(least(cast('61' as binary), 2)) as least_binary_num; +➤ least_binary_num[12,-1,0] 𝄀 +32 +select greatest(cast(2020 as year), 1999) as greatest_year_num; +➤ greatest_year_num[3,20,0] 𝄀 +2020 +select least(cast(2020 as year), 1999) as least_year_num; +➤ least_year_num[3,20,0] 𝄀 +1999 drop table if exists toll_transactions; drop table if exists mixed_num; drop table if exists uns; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 9fb031a77806e..748c544186d18 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -72,6 +72,31 @@ select least(cast(1.5 as decimal(10,1)), cast(2.25 as decimal(20,2))) as l_dec; select greatest(cast(1234567890123456789012345678901234567890.12 as decimal(60,2)), cast(2 as bigint)) as g_d256; select least(cast(1234567890123456789012345678901234567890.12 as decimal(60,2)), cast(2 as bigint)) as l_d256; +-- same-Oid DECIMAL256 values whose common width exceeds DECIMAL256 must use +-- the FLOAT64 fallback instead of comparing their different-scale raw values. +-- SQL DECIMAL precision is limited to 65, but these types require 65 + 65 = +-- 130 digits after scale alignment. +select greatest(cast(0.9 as decimal(65,65)), cast(1 as decimal(65,0))) as g_d256_scale_overflow; +select least(cast(0.9 as decimal(65,65)), cast(1 as decimal(65,0))) as l_d256_scale_overflow; + +-- issue #25215: mixed string and numeric arguments compare as strings. +select greatest('10', 2) as greatest_str_num; +select least('10', 2) as least_str_num; +select greatest(2, '10') as greatest_num_str; +select least(2, '10') as least_num_str; +select greatest('10', 2, 11.0) as greatest_three_mixed; +select least('10', 2, 11.0) as least_three_mixed; +select greatest('B', binary 'A') as greatest_nonbinary_binary; +select least('B', binary 'A') as least_nonbinary_binary; +select greatest(cast('10' as text), 2) as greatest_text_num; +select least(cast('10' as text), 2) as least_text_num; +select hex(greatest(cast('61' as blob), 2)) as greatest_blob_num; +select hex(least(cast('61' as blob), 2)) as least_blob_num; +select hex(greatest(cast('61' as binary), 2)) as greatest_binary_num; +select hex(least(cast('61' as binary), 2)) as least_binary_num; +select greatest(cast(2020 as year), 1999) as greatest_year_num; +select least(cast(2020 as year), 1999) as least_year_num; + drop table if exists toll_transactions; drop table if exists mixed_num; drop table if exists uns; From 7fa0ebc980b5ba7b4c12ab453085faf23cdd5d78 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 12:07:50 +0800 Subject: [PATCH 02/14] update --- .../function/func_builtin_leastgreatest.go | 50 ++++- .../func_builtin_leastgreatest_test.go | 46 +++- pkg/sql/plan/function/list_builtIn.go | 18 ++ .../function/greatest_least_numeric.result | 210 ++++++++++++++++++ .../cases/function/greatest_least_numeric.sql | 111 +++++++++ 5 files changed, 432 insertions(+), 3 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index a57c737d669d8..b71a21f46a5a3 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -51,6 +51,7 @@ const ( const ( leastGreatestNormalOverload = iota leastGreatestTemporalOverload + leastGreatestJSONTemporalOverload leastGreatestYearNumericOverload ) @@ -142,7 +143,9 @@ func resolveLeastGreatestType(inputs []types.Type) (leastGreatestResolution, boo if !ok { return leastGreatestResolution{}, false } - return leastGreatestPackedDateResolution(inputs, target), true + resolution := leastGreatestPackedDateResolution(inputs, target) + resolution.overloadID = leastGreatestJSONTemporalOverload + return resolution, true } if hasLeastGreatestJSON(nonNull) { @@ -889,7 +892,32 @@ func leastGreatestFnPackedDate( if !ok || resolution.comparisonMode != leastGreatestComparePackedDate { return moerr.NewInternalErrorNoCtx("unsupported temporal comparison resolution") } + return leastGreatestFnPackedDateResolved(parameters, result, proc, length, selectList, resolution, compareFn) +} + +func leastGreatestFnJSONPackedDate( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList, + compareFn func(v1, v2 types.Datetime) bool) error { + resolution := leastGreatestResolution{ + resultType: types.T_varchar.ToType(), + comparisonMode: leastGreatestComparePackedDate, + temporalItemType: leastGreatestTemporalItemType(vectorTypes(parameters)), + } + return leastGreatestFnPackedDateResolved(parameters, result, proc, length, selectList, resolution, compareFn) +} +func leastGreatestFnPackedDateResolved( + parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList, + resolution leastGreatestResolution, + compareFn func(v1, v2 types.Datetime) bool) error { rsVec := result.GetResultVector() rsNull := rsVec.GetNulls() if selectList != nil { @@ -1371,6 +1399,16 @@ func leastTemporalFn(parameters []*vector.Vector, }) } +func leastJSONTemporalFn(parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList) error { + return leastGreatestFnJSONPackedDate(parameters, result, proc, length, selectList, func(v1, v2 types.Datetime) bool { + return v1 < v2 + }) +} + func leastYearNumericFn(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, @@ -1684,6 +1722,16 @@ func greatestTemporalFn(parameters []*vector.Vector, }) } +func greatestJSONTemporalFn(parameters []*vector.Vector, + result vector.FunctionResultWrapper, + proc *process.Process, + length int, + selectList *FunctionSelectList) error { + return leastGreatestFnJSONPackedDate(parameters, result, proc, length, selectList, func(v1, v2 types.Datetime) bool { + return v1 > v2 + }) +} + func greatestYearNumericFn(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 4a39ddba7b0eb..a34239d791d7e 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -738,10 +738,19 @@ func TestLeastGreatestTemporalResolution(t *testing.T) { inputs: []types.Type{types.T_json.ToType(), types.T_date.ToType(), types.T_int64.ToType()}, wantOK: true, wantReturn: types.T_varchar, - wantOverload: leastGreatestTemporalOverload, + wantOverload: 2, wantMode: leastGreatestComparePackedDate, wantTargets: []types.T{types.T_varchar, types.T_date, types.T_varchar}, }, + { + name: "json date time keeps temporal inputs for json temporal overload", + inputs: []types.Type{types.T_json.ToType(), types.T_date.ToType(), types.T_time.ToType()}, + wantOK: true, + wantReturn: types.T_varchar, + wantOverload: 2, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_varchar, types.T_date, types.T_time}, + }, { name: "year date bigint keeps temporal peers", inputs: []types.Type{types.T_year.ToType(), types.T_date.ToType(), types.T_int64.ToType()}, @@ -786,7 +795,7 @@ func TestLeastGreatestHighPriorityResolution(t *testing.T) { }) require.True(t, ok) require.Equal(t, types.T_decimal128, resolution.resultType.Oid) - require.Equal(t, 2, resolution.overloadID) + require.Equal(t, leastGreatestYearNumericOverload, resolution.overloadID) require.Equal(t, []types.T{types.T_year, types.T_decimal128}, []types.T{ resolution.castTypes[0].Oid, resolution.castTypes[1].Oid, @@ -944,6 +953,27 @@ func TestLeastGreatestYearNumericFunctionResolution(t *testing.T) { } } +func TestLeastGreatestJSONTemporalFunctionResolution(t *testing.T) { + for _, name := range []string{"greatest", "least"} { + fn, err := GetFunctionByName(context.Background(), name, []types.Type{ + types.T_json.ToType(), + types.T_date.ToType(), + types.T_time.ToType(), + }) + require.NoError(t, err) + require.Equal(t, int32(2), fn.overloadId) + require.Equal(t, types.T_varchar, fn.GetReturnType().Oid) + + targets, shouldCast := fn.ShouldDoImplicitTypeCast() + require.True(t, shouldCast) + require.Equal(t, []types.T{types.T_varchar, types.T_date, types.T_time}, []types.T{ + targets[0].Oid, + targets[1].Oid, + targets[2].Oid, + }) + } +} + func TestLeastGreatestTemporalExecutor(t *testing.T) { proc := testutil.NewProcess(t) d1, err := types.ParseDateCast("2020-01-01") @@ -972,6 +1002,18 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { leastTemporalFn) ok, info = tcLeast.Run() require.True(t, ok, info) + + // JSON-temporal parameters reach this executor after JSON is cast to + // VARCHAR. The specialized overload keeps the VARCHAR return contract. + tcJSONGreatest := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(dateTyp, []types.Date{d1, d2}, nil), + NewFunctionTestInput(varcharTyp, []string{"2020-01-02", "2020-01-01"}, nil), + }, + NewFunctionTestResult(varcharTyp, false, []string{"2020-01-02", "2020-01-03"}, nil), + greatestJSONTemporalFn) + ok, info = tcJSONGreatest.Run() + require.True(t, ok, info) } // TestLeastGreatestWidthHelpers covers every branch of the integer-width → diff --git a/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index baf30a69ddbff..be2aac9159b1c 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -756,6 +756,15 @@ var supportedStringBuiltIns = []FuncNew{ retType: func(parameters []types.Type) types.Type { return leastGreatestReturnType(parameters) }, + newOp: func() executeLogicOfOverload { + return greatestJSONTemporalFn + }, + }, + { + overloadId: 3, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, newOp: func() executeLogicOfOverload { return greatestYearNumericFn }, @@ -2101,6 +2110,15 @@ var supportedStringBuiltIns = []FuncNew{ retType: func(parameters []types.Type) types.Type { return leastGreatestReturnType(parameters) }, + newOp: func() executeLogicOfOverload { + return leastJSONTemporalFn + }, + }, + { + overloadId: 3, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, newOp: func() executeLogicOfOverload { return leastYearNumericFn }, diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 22421dd6b28f8..92994bbbe1dd2 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -164,7 +164,217 @@ select greatest(cast(2020 as year), 1999) as greatest_year_num; select least(cast(2020 as year), 1999) as least_year_num; ➤ least_year_num[3,20,0] 𝄀 1999 +select greatest(json_extract('{"a":1}', '$'), '2') as greatest_json_varchar; +➤ greatest_json_varchar[12,-1,0] 𝄀 +{"a": 1} +select greatest(json_extract('{"a":1}', '$'), json_extract('{"b":2}', '$')) as greatest_json_json; +➤ greatest_json_json[12,-1,0] 𝄀 +{"b": 2} +select greatest(cast('2020-01-01' as date), json_extract('"2020-01-02"', '$')) as greatest_date_json; +➤ greatest_date_json[12,-1,0] 𝄀 +2020-01-02 +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast(2021 as year)) as greatest_json_date_year; +➤ greatest_json_date_year[12,-1,0] 𝄀 +2021-01-01 +select greatest(cast('2020-01-01' as date), '2020-01-02') as greatest_date_varchar; +➤ greatest_date_varchar[12,-1,0] 𝄀 +2020-01-02 +select greatest(cast('2020-01-01' as date), 'not-a-date'); +invalid input: invalid datetime value not-a-date +select least(cast('2020-01-01' as date), 'not-a-date'); +invalid input: invalid datetime value not-a-date +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time; +➤ greatest_date_time[93,64,0] 𝄀 +2026-07-14 12:00:00 +select greatest(cast(2020 as year), cast('2020-01-01' as date), 1) as greatest_year_date_num; +➤ greatest_year_date_num[12,-1,0] 𝄀 +2026-07-14 +select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as blob))) as greatest_date_blob; +➤ greatest_date_blob[12,-1,0] 𝄀 +323032302D30312D3032 +select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as varbinary))) as greatest_date_varbinary; +➤ greatest_date_varbinary[12,-1,0] 𝄀 +323032302D30312D3032 +select least(cast('10:00:00' as time), '09:00:00') as least_time_varchar; +➤ least_time_varchar[12,-1,0] 𝄀 +09:00:00 +select greatest(cast(2020 as year), cast(2021.5 as decimal(10,1))) as greatest_year_decimal; +➤ greatest_year_decimal[3,10,1] 𝄀 +2021.5 +select least(cast(2020 as year), cast(2019.5 as decimal(10,1))) as least_year_decimal; +➤ least_year_decimal[3,10,1] 𝄀 +2019.5 +select greatest(cast(2020 as year), 2021.5) as greatest_year_double; +➤ greatest_year_double[3,18,1] 𝄀 +2021.5 +select least(cast(2020 as year), cast(2021 as bit(16))) as least_year_bit; +➤ least_year_bit[-5,64,0] 𝄀 +2020 +select greatest(1999, cast(2020 as year), cast(2021 as year)) as greatest_multi_year_numeric; +➤ greatest_multi_year_numeric[3,20,0] 𝄀 +2021 +select greatest(cast('10:00:00' as time), 2) as greatest_time_numeric; +➤ greatest_time_numeric[12,-1,0] 𝄀 +2 +select greatest(cast('10:00:00' as time), cast(2 as bit(4))) as greatest_time_bit; +➤ greatest_time_bit[12,-1,0] 𝄀 +2 +select greatest(cast('10:00:00.1' as time(1)), cast('10:00:00.99' as time(2))) as greatest_time_scale; +➤ greatest_time_scale[92,64,0] 𝄀 +10:00:00.990000000 +select greatest(null, null) as greatest_all_null, least(null, null) as least_all_null; +➤ greatest_all_null[12,-1,0] ¦ least_all_null[12,-1,0] 𝄀 +null ¦ null +select greatest('10', null, 2) as greatest_mixed_null, least('10', null, 2) as least_mixed_null; +➤ greatest_mixed_null[12,-1,0] ¦ least_mixed_null[12,-1,0] 𝄀 +null ¦ null +select greatest(cast('a' as varchar), cast('b' as varchar)) as greatest_varchar_same_oid; +➤ greatest_varchar_same_oid[12,-1,0] 𝄀 +b +select least(cast('a' as blob), cast('b' as blob)) as least_blob_same_oid; +➤ least_blob_same_oid[12,0,0] 𝄀 +a +select greatest(cast(1.5 as decimal(10,1)), cast(1.49 as decimal(12,2))) as greatest_decimal_scale_align; +➤ greatest_decimal_scale_align[3,12,2] 𝄀 +1.50 +select least(cast(1.5 as decimal(10,1)), cast(1.49 as decimal(12,2))) as least_decimal_scale_align; +➤ least_decimal_scale_align[3,12,2] 𝄀 +1.49 +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.99' as datetime(2))) as greatest_datetime_scale; +➤ greatest_datetime_scale[93,64,0] 𝄀 +2020-01-01 00:00:00.990000000 +select greatest(cast('2020-01-01 00:00:00.1' as timestamp(1)), cast('2020-01-01 00:00:00.99' as timestamp(2))) as greatest_timestamp_scale; +➤ greatest_timestamp_scale[93,64,0] 𝄀 +2020-01-01 00:00:00.990000000 +select least(json_extract('{"a":1}', '$'), json_extract('{"b":2}', '$')) as least_json_json; +➤ least_json_json[12,-1,0] 𝄀 +{"a": 1} +select greatest(json_extract('"b"', '$'), cast('a' as text)) as greatest_json_text; +➤ greatest_json_text[12,0,0] 𝄀 +b +select greatest(json_extract('10', '$'), 2) as greatest_json_numeric; +➤ greatest_json_numeric[12,-1,0] 𝄀 +2 +select greatest(json_extract('"10:00:00"', '$'), cast('09:00:00' as time)) as greatest_json_time; +➤ greatest_json_time[12,-1,0] 𝄀 +10:00:00 +select greatest(json_extract('2020', '$'), cast(2019 as year)) as greatest_json_year; +➤ greatest_json_year[12,-1,0] 𝄀 +2020 +select greatest(json_extract('1', '$'), cast('1' as blob)); +invalid argument function greatest, bad value [JSON BLOB] +select greatest(json_extract('1', '$'), cast('1' as binary)); +invalid argument function greatest, bad value [JSON BINARY] +select greatest(json_extract('1', '$'), cast('1' as varbinary)); +invalid argument function greatest, bad value [JSON VARBINARY] +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('x' as text)); +invalid argument function greatest, bad value [JSON DATE TEXT] +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('2020-01-03' as blob)); +invalid argument function greatest, bad value [JSON DATE BLOB] +select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00:00:00' as datetime)) as greatest_json_datetime; +➤ greatest_json_datetime[12,-1,0] 𝄀 +2020-01-02 12:00:00 +select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00:00:00' as timestamp)) as greatest_json_timestamp; +➤ greatest_json_timestamp[12,-1,0] 𝄀 +2020-01-02 12:00:00 +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), 20200103) as greatest_json_date_bigint; +➤ greatest_json_date_bigint[12,-1,0] 𝄀 +2020-01-03 +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_json_date_time; +➤ greatest_json_date_time[12,-1,0] 𝄀 +2026-07-14 +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), interval 1 day); +invalid argument function greatest, bad value [JSON DATE INTERVAL] +select greatest(cast('2020-01-01' as datetime), '2020-01-02') as greatest_datetime_varchar; +➤ greatest_datetime_varchar[12,-1,0] 𝄀 +2020-01-02 00:00:00 +select greatest(cast('2020-01-01' as date), cast('2020-01-01 12:00:00' as datetime)) as greatest_date_datetime; +➤ greatest_date_datetime[93,64,0] 𝄀 +2020-01-01 12:00:00 +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time_format; +➤ greatest_date_time_format[93,64,0] 𝄀 +2026-07-14 12:00:00 +select greatest(cast('2020-01-01' as date), cast('2020-01-02' as text)) as greatest_date_text; +➤ greatest_date_text[12,0,0] 𝄀 +2020-01-02 +select least(cast('10:00:00' as time), cast('09:00:00' as text)) as least_time_text; +➤ least_time_text[12,0,0] 𝄀 +09:00:00 +select greatest(cast('10:00:00' as time), cast('09:00:00' as blob)) as greatest_time_blob; +➤ greatest_time_blob[12,0,0] 𝄀 +10:00:00 +select greatest(cast('2020-01-01' as date), 20200102) as greatest_date_numeric; +➤ greatest_date_numeric[12,-1,0] 𝄀 +2020-01-02 +select greatest(cast('2020-01-01' as date), cast(20200102 as bit(25))) as greatest_date_bit; +➤ greatest_date_bit[12,-1,0] 𝄀 +2020-01-02 +select greatest(cast(2020 as year), cast('10:00:00' as time)) as greatest_year_time; +➤ greatest_year_time[12,-1,0] 𝄀 +2020 +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), '2020-01-02') as greatest_date_time_varchar; +➤ greatest_date_time_varchar[12,-1,0] 𝄀 +2026-07-14 +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), 20200102) as greatest_date_time_bigint; +➤ greatest_date_time_bigint[12,-1,0] 𝄀 +2026-07-14 +select hex(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), cast('2020-01-02' as blob))) as greatest_date_time_blob; +➤ greatest_date_time_blob[12,-1,0] 𝄀 +323032362D30372D3134 +select greatest(cast('b' as text), cast('a' as blob)) as greatest_text_blob; +➤ greatest_text_blob[12,0,0] 𝄀 +b +select greatest(cast('b' as char), cast('a' as varbinary)) as greatest_char_varbinary; +➤ greatest_char_varbinary[12,-1,0] 𝄀 +b +select hex(greatest(cast('a' as blob), cast('b' as varbinary))) as greatest_blob_varbinary; +➤ greatest_blob_varbinary[12,-1,0] 𝄀 +62 +select hex(greatest(cast('a' as binary), cast('b' as varbinary))) as greatest_binary_varbinary; +➤ greatest_binary_varbinary[12,-1,0] 𝄀 +62 +drop table if exists greatest_least_supported_oid; +create table greatest_least_supported_oid (id int primary key, v32 vecf32(3), v64 vecf64(3)); +insert into greatest_least_supported_oid values (1, '[1,2,3]', '[1,2,3]'); +select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), cast('00000000-0000-0000-0000-000000000002' as uuid)) as greatest_uuid_same_oid; +➤ greatest_uuid_same_oid[12,-1,0] 𝄀 +00000000-0000-0000-0000-000000000002 +select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); +invalid argument function greatest, bad value [UUID VARCHAR] +select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; +➤ greatest_rowid_same_oid[12,0,0] 𝄀 +019f5ec9-dc42-75e2-a1e4-3e5825928859-0-0-0 +select greatest(__mo_rowid, 1) from greatest_least_supported_oid; +invalid argument function greatest, bad value [ROWID BIGINT] +select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; +➤ greatest_vecf32_same_oid[12,3,0] 𝄀 +[1, 2, 3] +select greatest(v32, v64) from greatest_least_supported_oid; +invalid argument function greatest, bad value [VECF32 VECF64] +select greatest(cast('file://greatest-least-a' as datalink), cast('file://greatest-least-b' as datalink)) as greatest_datalink_same_oid; +➤ greatest_datalink_same_oid[12,0,0] 𝄀 +file://greatest-least-b +select greatest(cast('file://greatest-least-a' as datalink), 'x'); +invalid argument function greatest, bad value [DATALINK VARCHAR] +select greatest(interval 1 day, interval 2 day); +invalid argument function greatest, bad value [INTERVAL INTERVAL] +select greatest(interval 1 day, cast('2020-01-01' as date)); +invalid argument function greatest, bad value [INTERVAL DATE] +select greatest(st_geomfromtext('POINT(1 1)'), st_geomfromtext('POINT(2 2)')); +invalid argument function greatest, bad value [GEOMETRY GEOMETRY] +select greatest(st_geomfromtext('POINT(1 1)'), 'x'); +invalid argument function greatest, bad value [GEOMETRY VARCHAR] +drop table if exists greatest_least_enum; +create table greatest_least_enum (e enum('a', 'b'), d date); +insert into greatest_least_enum values ('a', '2020-01-01'); +select greatest(e, e) from greatest_least_enum; +➤ greatest(e, e)[12,-1,0] 𝄀 +a +select greatest(e, d) from greatest_least_enum; +invalid input: invalid datetime value a drop table if exists toll_transactions; drop table if exists mixed_num; drop table if exists uns; drop table if exists bt; +drop table if exists greatest_least_supported_oid; +drop table if exists greatest_least_enum; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 748c544186d18..956277021be1a 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -97,7 +97,118 @@ select hex(least(cast('61' as binary), 2)) as least_binary_num; select greatest(cast(2020 as year), 1999) as greatest_year_num; select least(cast(2020 as year), 1999) as least_year_num; +-- JSON and date-bearing temporal use the dedicated json-temporal overload. +select greatest(json_extract('{"a":1}', '$'), '2') as greatest_json_varchar; +select greatest(json_extract('{"a":1}', '$'), json_extract('{"b":2}', '$')) as greatest_json_json; +select greatest(cast('2020-01-01' as date), json_extract('"2020-01-02"', '$')) as greatest_date_json; +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast(2021 as year)) as greatest_json_date_year; + +-- Date-bearing temporal values compare as packed datetime values. Invalid +-- temporal text returns MatrixOne's invalid-input error. +select greatest(cast('2020-01-01' as date), '2020-01-02') as greatest_date_varchar; +select greatest(cast('2020-01-01' as date), 'not-a-date'); +select least(cast('2020-01-01' as date), 'not-a-date'); +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time; +select greatest(cast(2020 as year), cast('2020-01-01' as date), 1) as greatest_year_date_num; +select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as blob))) as greatest_date_blob; +select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as varbinary))) as greatest_date_varbinary; + +-- TIME without a date-bearing peer remains in the text domain. +select least(cast('10:00:00' as time), '09:00:00') as least_time_varchar; + +-- YEAR + numeric keeps the YEAR vector and uses the year-numeric overload. +select greatest(cast(2020 as year), cast(2021.5 as decimal(10,1))) as greatest_year_decimal; +select least(cast(2020 as year), cast(2019.5 as decimal(10,1))) as least_year_decimal; +select greatest(cast(2020 as year), 2021.5) as greatest_year_double; +select least(cast(2020 as year), cast(2021 as bit(16))) as least_year_bit; +select greatest(1999, cast(2020 as year), cast(2021 as year)) as greatest_multi_year_numeric; +select greatest(cast('10:00:00' as time), 2) as greatest_time_numeric; +select greatest(cast('10:00:00' as time), cast(2 as bit(4))) as greatest_time_bit; + +-- Same-Oid temporal metadata aligns scale before comparison. +select greatest(cast('10:00:00.1' as time(1)), cast('10:00:00.99' as time(2))) as greatest_time_scale; + +-- All T_any arguments return a VARCHAR NULL result, and any NULL argument +-- makes the strict function result NULL. +select greatest(null, null) as greatest_all_null, least(null, null) as least_all_null; +select greatest('10', null, 2) as greatest_mixed_null, least('10', null, 2) as least_mixed_null; + +-- Same-Oid normal executor paths and metadata alignment value semantics. +select greatest(cast('a' as varchar), cast('b' as varchar)) as greatest_varchar_same_oid; +select least(cast('a' as blob), cast('b' as blob)) as least_blob_same_oid; +select greatest(cast(1.5 as decimal(10,1)), cast(1.49 as decimal(12,2))) as greatest_decimal_scale_align; +select least(cast(1.5 as decimal(10,1)), cast(1.49 as decimal(12,2))) as least_decimal_scale_align; +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.99' as datetime(2))) as greatest_datetime_scale; +select greatest(cast('2020-01-01 00:00:00.1' as timestamp(1)), cast('2020-01-01 00:00:00.99' as timestamp(2))) as greatest_timestamp_scale; + +-- JSON rules: pure JSON is text, allowed text peers choose TEXT/VARCHAR, and +-- JSON is rejected before any binary or unsupported mixed-type rule. +select least(json_extract('{"a":1}', '$'), json_extract('{"b":2}', '$')) as least_json_json; +select greatest(json_extract('"b"', '$'), cast('a' as text)) as greatest_json_text; +select greatest(json_extract('10', '$'), 2) as greatest_json_numeric; +select greatest(json_extract('"10:00:00"', '$'), cast('09:00:00' as time)) as greatest_json_time; +select greatest(json_extract('2020', '$'), cast(2019 as year)) as greatest_json_year; +select greatest(json_extract('1', '$'), cast('1' as blob)); +select greatest(json_extract('1', '$'), cast('1' as binary)); +select greatest(json_extract('1', '$'), cast('1' as varbinary)); +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('x' as text)); +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('2020-01-03' as blob)); +select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00:00:00' as datetime)) as greatest_json_datetime; +select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00:00:00' as timestamp)) as greatest_json_timestamp; +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), 20200103) as greatest_json_date_bigint; +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_json_date_time; +select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), interval 1 day); + +-- The date-bearing temporal matrix uses packed datetime comparison and chooses +-- the documented result family from the non-temporal peer. +select greatest(cast('2020-01-01' as datetime), '2020-01-02') as greatest_datetime_varchar; +select greatest(cast('2020-01-01' as date), cast('2020-01-01 12:00:00' as datetime)) as greatest_date_datetime; +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time_format; +select greatest(cast('2020-01-01' as date), cast('2020-01-02' as text)) as greatest_date_text; +select least(cast('10:00:00' as time), cast('09:00:00' as text)) as least_time_text; +select greatest(cast('10:00:00' as time), cast('09:00:00' as blob)) as greatest_time_blob; +select greatest(cast('2020-01-01' as date), 20200102) as greatest_date_numeric; +select greatest(cast('2020-01-01' as date), cast(20200102 as bit(25))) as greatest_date_bit; +select greatest(cast(2020 as year), cast('10:00:00' as time)) as greatest_year_time; +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), '2020-01-02') as greatest_date_time_varchar; +select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), 20200102) as greatest_date_time_bigint; +select hex(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), cast('2020-01-02' as blob))) as greatest_date_time_blob; + +-- String-family precedence is TEXT > CHAR/VARCHAR > BLOB > BINARY/VARBINARY. +select greatest(cast('b' as text), cast('a' as blob)) as greatest_text_blob; +select greatest(cast('b' as char), cast('a' as varbinary)) as greatest_char_varbinary; +select hex(greatest(cast('a' as blob), cast('b' as varbinary))) as greatest_blob_varbinary; +select hex(greatest(cast('a' as binary), cast('b' as varbinary))) as greatest_binary_varbinary; + +-- Existing executor-supported Oids remain valid only for same-Oid calls; +-- every corresponding mixed call is rejected before a normal executor runs. +drop table if exists greatest_least_supported_oid; +create table greatest_least_supported_oid (id int primary key, v32 vecf32(3), v64 vecf64(3)); +insert into greatest_least_supported_oid values (1, '[1,2,3]', '[1,2,3]'); +select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), cast('00000000-0000-0000-0000-000000000002' as uuid)) as greatest_uuid_same_oid; +select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); +select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; +select greatest(__mo_rowid, 1) from greatest_least_supported_oid; +select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; +select greatest(v32, v64) from greatest_least_supported_oid; +select greatest(cast('file://greatest-least-a' as datalink), cast('file://greatest-least-b' as datalink)) as greatest_datalink_same_oid; +select greatest(cast('file://greatest-least-a' as datalink), 'x'); + +-- Unsupported Oids must fail both for same-Oid and mixed calls rather than +-- reaching the normal executor's unreachable branch. +select greatest(interval 1 day, interval 2 day); +select greatest(interval 1 day, cast('2020-01-01' as date)); +select greatest(st_geomfromtext('POINT(1 1)'), st_geomfromtext('POINT(2 2)')); +select greatest(st_geomfromtext('POINT(1 1)'), 'x'); +drop table if exists greatest_least_enum; +create table greatest_least_enum (e enum('a', 'b'), d date); +insert into greatest_least_enum values ('a', '2020-01-01'); +select greatest(e, e) from greatest_least_enum; +select greatest(e, d) from greatest_least_enum; + drop table if exists toll_transactions; drop table if exists mixed_num; drop table if exists uns; drop table if exists bt; +drop table if exists greatest_least_supported_oid; +drop table if exists greatest_least_enum; From 6923cc18e213d5173590ec95014228375e76d5e0 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 12:51:50 +0800 Subject: [PATCH 03/14] update --- .../plan/base_binder_leastgreatest_test.go | 64 ++++++++++++++ .../function/func_builtin_leastgreatest.go | 83 ++++++++++++++++++- .../func_builtin_leastgreatest_test.go | 65 +++++++++++++++ .../function/greatest_least_numeric.result | 8 +- .../cases/function/greatest_least_numeric.sql | 5 +- 5 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 pkg/sql/plan/base_binder_leastgreatest_test.go diff --git a/pkg/sql/plan/base_binder_leastgreatest_test.go b/pkg/sql/plan/base_binder_leastgreatest_test.go new file mode 100644 index 0000000000000..baf4176e085b0 --- /dev/null +++ b/pkg/sql/plan/base_binder_leastgreatest_test.go @@ -0,0 +1,64 @@ +// Copyright 2026 Matrix Origin +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package plan + +import ( + "context" + "testing" + + "github.com/matrixorigin/matrixone/pkg/container/types" + planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" + "github.com/matrixorigin/matrixone/pkg/sql/parsers" + "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" + "github.com/stretchr/testify/require" +) + +func TestBindLeastGreatestTemporalScale(t *testing.T) { + for _, oid := range []types.T{types.T_time, types.T_datetime, types.T_timestamp} { + t.Run(oid.String(), func(t *testing.T) { + args := []*planpb.Expr{ + {Typ: planpb.Type{Id: int32(oid), Width: 64, Scale: 1}}, + {Typ: planpb.Type{Id: int32(oid), Width: 64, Scale: 4}}, + } + for _, name := range []string{"greatest", "least"} { + expr, err := BindFuncExprImplByPlanExpr(context.Background(), name, args) + require.NoError(t, err, name) + require.Equal(t, int32(oid), expr.Typ.Id, name) + require.Equal(t, int32(4), expr.Typ.Scale, name) + } + }) + } +} + +func TestBuildLeastGreatestTemporalScale(t *testing.T) { + stmt, err := parsers.ParseOne(context.Background(), dialect.MYSQL, + "select greatest(cast('10:00:00.1' as time(1)), cast('10:00:00.99' as time(2)))", 1) + require.NoError(t, err) + + pl, err := BuildPlan(NewMockCompilerContext(true), stmt, false) + require.NoError(t, err) + + var result *planpb.Expr + for _, node := range pl.GetQuery().Nodes { + for _, expr := range node.ProjectList { + if expr.GetF() != nil && expr.GetF().GetFunc().GetObjName() == "greatest" { + result = expr + } + } + } + require.NotNil(t, result) + require.Equal(t, int32(types.T_time), result.Typ.Id) + require.Equal(t, int32(2), result.Typ.Scale) +} diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index b71a21f46a5a3..d2c6f3a560b00 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -109,6 +109,23 @@ func resolveLeastGreatestType(inputs []types.Type) (leastGreatestResolution, boo }, true } + if hasLeastGreatestEnum(nonNull) { + normalized := append([]types.Type(nil), inputs...) + for i := range normalized { + if normalized[i].Oid == types.T_enum { + normalized[i] = types.T_varchar.ToType() + } + } + resolution, ok := resolveLeastGreatestType(normalized) + if !ok { + return leastGreatestResolution{}, false + } + if len(resolution.castTypes) == 0 { + resolution.castTypes = leastGreatestCastTypes(inputs, resolution.resultType) + } + return resolution, true + } + // Fast path: every non-NULL argument already shares the same type Oid. This // preserves the original behavior (including any per-type scale handling) for // the common case where no promotion is needed. @@ -262,6 +279,15 @@ func hasLeastGreatestYear(inputs []types.Type) bool { return false } +func hasLeastGreatestEnum(inputs []types.Type) bool { + for i := range inputs { + if inputs[i].Oid == types.T_enum { + return true + } + } + return false +} + func leastGreatestExecutorSupportsOid(oid types.T) bool { switch oid { case types.T_bool, types.T_bit, @@ -966,7 +992,7 @@ func leastGreatestFnPackedDateResolved( } goto nextRow } - v, err := leastGreatestDatetimeValue(pv, i, loc) + v, err := leastGreatestDatetimeValue(pv, i, proc, loc) if err != nil { return err } @@ -1000,7 +1026,7 @@ func leastGreatestSetNullResult(result vector.FunctionResultWrapper, length int) return nil } -func leastGreatestDatetimeValue(v *vector.Vector, row uint64, loc *time.Location) (types.Datetime, error) { +func leastGreatestDatetimeValue(v *vector.Vector, row uint64, proc *process.Process, loc *time.Location) (types.Datetime, error) { switch v.GetType().Oid { case types.T_date: p := vector.GenerateFunctionFixedTypeParameter[types.Date](v) @@ -1017,7 +1043,7 @@ func leastGreatestDatetimeValue(v *vector.Vector, row uint64, loc *time.Location case types.T_time: p := vector.GenerateFunctionFixedTypeParameter[types.Time](v) val, _ := p.GetValue(row) - return val.ToDatetime(v.GetType().Scale), nil + return leastGreatestTimeToDatetime(val, v.GetType().Scale, proc, loc), nil case types.T_year: p := vector.GenerateFunctionFixedTypeParameter[types.MoYear](v) val, _ := p.GetValue(row) @@ -1029,6 +1055,37 @@ func leastGreatestDatetimeValue(v *vector.Vector, row uint64, loc *time.Location } } +// leastGreatestTimeToDatetime follows MySQL's temporal comparison rule: when +// TIME must be compared as a datetime, it is combined with the statement's +// start date in the session time zone. Do not use Time.ToDatetime here: that +// helper uses the instantaneous UTC date, which can differ within a query and +// from the session-local date. +func leastGreatestTimeToDatetime(value types.Time, scale int32, proc *process.Process, loc *time.Location) types.Datetime { + if loc == nil { + loc = time.Local + } + + queryStart := time.Time{} + if proc != nil { + queryStart = proc.GetStmtProfile().GetQueryStart() + if queryStart.IsZero() && proc.GetUnixTime() != 0 { + queryStart = time.Unix(0, proc.GetUnixTime()) + } + } + if queryStart.IsZero() { + queryStart = time.Now() + } + queryStart = queryStart.In(loc) + + base := types.DatetimeFromClock( + int32(queryStart.Year()), + uint8(queryStart.Month()), + uint8(queryStart.Day()), + 0, 0, 0, 0, + ) + return base + types.Datetime(value.TruncateToScale(scale)) +} + func leastGreatestParseDatetimeBytes(v []byte) (types.Datetime, error) { s := string(v) if dt, err := types.ParseDatetime(s, 6); err == nil { @@ -1095,11 +1152,30 @@ func leastGreatestParamType(parameters []*vector.Vector) types.Type { return types.T_varchar.ToType() } +// leastGreatestRestoreTemporalResultType keeps runtime result metadata aligned +// with the resolver. Some execution paths can allocate the result wrapper +// before the aligned TIME/DATETIME/TIMESTAMP scale reaches it. +func leastGreatestRestoreTemporalResultType(parameters []*vector.Vector, result vector.FunctionResultWrapper) { + resolution, ok := resolveLeastGreatestType(vectorTypes(parameters)) + if !ok { + return + } + + switch resolution.resultType.Oid { + case types.T_time, types.T_datetime, types.T_timestamp: + resultType := result.GetResultVector().GetType() + if resultType.Oid == resolution.resultType.Oid && !resultType.Eq(resolution.resultType) { + result.GetResultVector().SetType(resolution.resultType) + } + } +} + func leastFn(parameters []*vector.Vector, result vector.FunctionResultWrapper, proc *process.Process, length int, selectList *FunctionSelectList) error { + leastGreatestRestoreTemporalResultType(parameters, result) paramType := leastGreatestParamType(parameters) switch paramType.Oid { case types.T_bool: @@ -1424,6 +1500,7 @@ func greatestFn(parameters []*vector.Vector, proc *process.Process, length int, selectList *FunctionSelectList) error { + leastGreatestRestoreTemporalResultType(parameters, result) paramType := leastGreatestParamType(parameters) switch paramType.Oid { case types.T_bool: diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index a34239d791d7e..df5429f3b09c2 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -18,9 +18,11 @@ import ( "context" "fmt" "testing" + "time" "github.com/matrixorigin/matrixone/pkg/container/types" "github.com/matrixorigin/matrixone/pkg/testutil" + "github.com/matrixorigin/matrixone/pkg/vm/process" "github.com/stretchr/testify/require" ) @@ -686,6 +688,20 @@ func TestLeastGreatestFunctionResolution(t *testing.T) { wantCast: true, wantTargets: []types.T{types.T_varbinary, types.T_varbinary}, }, + { + name: "enum varchar returns varchar", + args: []types.Type{types.T_enum.ToType(), types.T_varchar.ToType()}, + wantReturn: types.T_varchar, + wantCast: true, + wantTargets: []types.T{types.T_varchar, types.T_varchar}, + }, + { + name: "enum enum returns varchar", + args: []types.Type{types.T_enum.ToType(), types.T_enum.ToType()}, + wantReturn: types.T_varchar, + wantCast: true, + wantTargets: []types.T{types.T_varchar, types.T_varchar}, + }, } for _, c := range cases { @@ -1016,6 +1032,55 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { require.True(t, ok, info) } +func TestLeastGreatestPackedDateUsesStatementStartForTime(t *testing.T) { + proc := testutil.NewProcess(t) + loc := time.FixedZone("UTC+8", 8*60*60) + proc.GetSessionInfo().TimeZone = loc + stmtProfile := &process.StmtProfile{} + stmtProfile.SetQueryStart(time.Date(2024, 5, 6, 1, 2, 3, 0, loc)) + proc.SetStmtProfile(stmtProfile) + + date, err := types.ParseDateCast("2020-01-01") + require.NoError(t, err) + timeValue, err := types.ParseTime("12:34:56", 0) + require.NoError(t, err) + + resultType := types.T_datetime.ToType() + tc := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(types.T_date.ToType(), []types.Date{date}, nil), + NewFunctionTestInput(types.T_time.ToType(), []types.Time{timeValue}, nil), + }, + NewFunctionTestResult(resultType, false, + []types.Datetime{types.DatetimeFromClock(2024, 5, 6, 12, 34, 56, 0)}, nil), + greatestTemporalFn) + ok, info := tc.Run() + require.True(t, ok, info) +} + +func TestLeastGreatestNormalExecutorRestoresTemporalScale(t *testing.T) { + proc := testutil.NewProcess(t) + timeScale1 := types.New(types.T_time, 64, 1) + timeScale2 := types.New(types.T_time, 64, 2) + first, err := types.ParseTime("10:00:00.1", timeScale1.Scale) + require.NoError(t, err) + second, err := types.ParseTime("10:00:00.99", timeScale2.Scale) + require.NoError(t, err) + + // Simulate an execution path whose result wrapper was initialized before + // the resolver's aligned temporal metadata reached it. + tc := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(timeScale1, []types.Time{first}, nil), + NewFunctionTestInput(timeScale2, []types.Time{second}, nil), + }, + NewFunctionTestResult(types.New(types.T_time, 64, 0), false, []types.Time{second}, nil), + greatestFn) + ok, info := tc.Run() + require.True(t, ok, info) + require.Equal(t, int32(2), tc.GetResultVectorDirectly().GetType().Scale) +} + // TestLeastGreatestWidthHelpers covers every branch of the integer-width → // type helpers used by the LEAST/GREATEST promotion logic. func TestLeastGreatestWidthHelpers(t *testing.T) { diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 92994bbbe1dd2..4f626a121c6c7 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -343,7 +343,7 @@ select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); invalid argument function greatest, bad value [UUID VARCHAR] select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; ➤ greatest_rowid_same_oid[12,0,0] 𝄀 -019f5ec9-dc42-75e2-a1e4-3e5825928859-0-0-0 +019f5eef-b733-705c-bde4-8ec6ae5317bf-0-0-0 select greatest(__mo_rowid, 1) from greatest_least_supported_oid; invalid argument function greatest, bad value [ROWID BIGINT] select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; @@ -370,6 +370,12 @@ insert into greatest_least_enum values ('a', '2020-01-01'); select greatest(e, e) from greatest_least_enum; ➤ greatest(e, e)[12,-1,0] 𝄀 a +select greatest(e, 'b') from greatest_least_enum; +➤ greatest(e, b)[12,-1,0] 𝄀 +b +select least(e, 'b') from greatest_least_enum; +➤ least(e, b)[12,-1,0] 𝄀 +a select greatest(e, d) from greatest_least_enum; invalid input: invalid datetime value a drop table if exists toll_transactions; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 956277021be1a..a0298318bb259 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -195,7 +195,8 @@ select greatest(cast('file://greatest-least-a' as datalink), cast('file://greate select greatest(cast('file://greatest-least-a' as datalink), 'x'); -- Unsupported Oids must fail both for same-Oid and mixed calls rather than --- reaching the normal executor's unreachable branch. +-- reaching the normal executor's unreachable branch. ENUM is normalized to +-- VARCHAR before function resolution, so it follows the string/temporal rules. select greatest(interval 1 day, interval 2 day); select greatest(interval 1 day, cast('2020-01-01' as date)); select greatest(st_geomfromtext('POINT(1 1)'), st_geomfromtext('POINT(2 2)')); @@ -204,6 +205,8 @@ drop table if exists greatest_least_enum; create table greatest_least_enum (e enum('a', 'b'), d date); insert into greatest_least_enum values ('a', '2020-01-01'); select greatest(e, e) from greatest_least_enum; +select greatest(e, 'b') from greatest_least_enum; +select least(e, 'b') from greatest_least_enum; select greatest(e, d) from greatest_least_enum; drop table if exists toll_transactions; From 80a45c3eec218bf497481829aa88c64e18f5b310 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 15:14:17 +0800 Subject: [PATCH 04/14] update --- .../plan/base_binder_leastgreatest_test.go | 42 +++++++++++++++++ .../function/func_builtin_leastgreatest.go | 39 ++++++++++----- .../func_builtin_leastgreatest_test.go | 15 ++++++ .../function/greatest_least_numeric.result | 47 +++++++++---------- .../cases/function/greatest_least_numeric.sql | 15 +++--- 5 files changed, 114 insertions(+), 44 deletions(-) diff --git a/pkg/sql/plan/base_binder_leastgreatest_test.go b/pkg/sql/plan/base_binder_leastgreatest_test.go index baf4176e085b0..84ecacc28163e 100644 --- a/pkg/sql/plan/base_binder_leastgreatest_test.go +++ b/pkg/sql/plan/base_binder_leastgreatest_test.go @@ -22,6 +22,7 @@ import ( planpb "github.com/matrixorigin/matrixone/pkg/pb/plan" "github.com/matrixorigin/matrixone/pkg/sql/parsers" "github.com/matrixorigin/matrixone/pkg/sql/parsers/dialect" + "github.com/matrixorigin/matrixone/pkg/sql/plan/rule" "github.com/stretchr/testify/require" ) @@ -62,3 +63,44 @@ func TestBuildLeastGreatestTemporalScale(t *testing.T) { require.Equal(t, int32(types.T_time), result.Typ.Id) require.Equal(t, int32(2), result.Typ.Scale) } + +func TestConstantFoldLeastGreatestTemporalScale(t *testing.T) { + ctx := NewMockCompilerContext(true) + stmt, err := parsers.ParseOne(context.Background(), dialect.MYSQL, + "select greatest(cast('10:00:00.1' as time(1)), cast('10:00:00.99' as time(2)))", 1) + require.NoError(t, err) + + pl, err := BuildPlan(ctx, stmt, false) + require.NoError(t, err) + + var result *planpb.Expr + for _, node := range pl.GetQuery().Nodes { + for _, expr := range node.ProjectList { + if expr.GetF() != nil && expr.GetF().GetFunc().GetObjName() == "greatest" { + result = expr + } + } + } + require.NotNil(t, result) + require.Len(t, result.GetF().Args, 2) + require.Equal(t, int32(2), result.GetF().Args[0].Typ.Scale) + require.Equal(t, int32(2), result.GetF().Args[1].Typ.Scale) + require.Equal(t, int32(2), result.Typ.Scale) + + fold := rule.NewConstantFold(false) + foldOne := func(expr *planpb.Expr) *planpb.Expr { + node := &planpb.Node{ProjectList: []*planpb.Expr{DeepCopyExpr(expr)}} + fold.Apply(node, nil, ctx.GetProcess()) + return node.ProjectList[0] + } + + firstCast := foldOne(result.GetF().Args[0]) + secondCast := foldOne(result.GetF().Args[1]) + foldedGreatest := foldOne(result) + + // The resolver adds an implicit TIME(2) cast around TIME(1), so both + // arguments must be TIME(2) before the outer function is folded. + require.Equal(t, int32(2), firstCast.Typ.Scale, "first argument") + require.Equal(t, int32(2), secondCast.Typ.Scale, "TIME(2) cast") + require.Equal(t, int32(2), foldedGreatest.Typ.Scale, "GREATEST result") +} diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index d2c6f3a560b00..a22883ce460f8 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -992,7 +992,7 @@ func leastGreatestFnPackedDateResolved( } goto nextRow } - v, err := leastGreatestDatetimeValue(pv, i, proc, loc) + v, err := leastGreatestDatetimeValue(pv, i, resolution.temporalItemType, proc, loc) if err != nil { return err } @@ -1026,7 +1026,7 @@ func leastGreatestSetNullResult(result vector.FunctionResultWrapper, length int) return nil } -func leastGreatestDatetimeValue(v *vector.Vector, row uint64, proc *process.Process, loc *time.Location) (types.Datetime, error) { +func leastGreatestDatetimeValue(v *vector.Vector, row uint64, temporalItemType types.Type, proc *process.Process, loc *time.Location) (types.Datetime, error) { switch v.GetType().Oid { case types.T_date: p := vector.GenerateFunctionFixedTypeParameter[types.Date](v) @@ -1049,7 +1049,7 @@ func leastGreatestDatetimeValue(v *vector.Vector, row uint64, proc *process.Proc val, _ := p.GetValue(row) return types.DatetimeFromClock(int32(val.ToInt64()), 1, 1, 0, 0, 0, 0), nil case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: - return leastGreatestParseDatetimeBytes(v.GetBytesAt(int(row))) + return leastGreatestParsePackedDateBytes(v.GetBytesAt(int(row)), temporalItemType, proc, loc) default: return types.Datetime(0), moerr.NewInternalErrorNoCtxf("unsupported temporal comparison type %s", v.GetType().Oid.String()) } @@ -1086,19 +1086,32 @@ func leastGreatestTimeToDatetime(value types.Time, scale int32, proc *process.Pr return base + types.Datetime(value.TruncateToScale(scale)) } -func leastGreatestParseDatetimeBytes(v []byte) (types.Datetime, error) { +func leastGreatestParsePackedDateBytes(v []byte, temporalItemType types.Type, proc *process.Process, loc *time.Location) (types.Datetime, error) { s := string(v) - if dt, err := types.ParseDatetime(s, 6); err == nil { - return dt, nil - } - if d, err := types.ParseDateCast(s); err == nil { + switch temporalItemType.Oid { + case types.T_date: + d, err := types.ParseDateCast(s) + if err != nil { + return types.Datetime(0), err + } return d.ToDatetime(), nil + case types.T_datetime: + return types.ParseDatetime(s, temporalItemType.Scale) + case types.T_timestamp: + ts, err := types.ParseTimestamp(loc, s, temporalItemType.Scale) + if err != nil { + return types.Datetime(0), err + } + return ts.ToDatetime(loc), nil + case types.T_time: + t, err := types.ParseTime(s, temporalItemType.Scale) + if err != nil { + return types.Datetime(0), err + } + return leastGreatestTimeToDatetime(t, temporalItemType.Scale, proc, loc), nil + default: + return types.Datetime(0), moerr.NewInternalErrorNoCtxf("unsupported temporal comparison target %s", temporalItemType.Oid.String()) } - if t, err := types.ParseTime(s, 6); err == nil { - return t.ToDatetime(6), nil - } - _, err := types.ParseDatetime(s, 6) - return types.Datetime(0), err } func leastGreatestAppendPackedDateResult( diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index df5429f3b09c2..76e63ff3d1dfa 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -1019,6 +1019,16 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { ok, info = tcLeast.Run() require.True(t, ok, info) + tcInvalidDatePeer := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(dateTyp, []types.Date{d1}, nil), + NewFunctionTestInput(varcharTyp, []string{"1"}, nil), + }, + NewFunctionTestResult(varcharTyp, true, []string{""}, nil), + greatestTemporalFn) + ok, info = tcInvalidDatePeer.Run() + require.True(t, ok, info) + // JSON-temporal parameters reach this executor after JSON is cast to // VARCHAR. The specialized overload keeps the VARCHAR return contract. tcJSONGreatest := NewFunctionTestCase(proc, @@ -1032,6 +1042,11 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { require.True(t, ok, info) } +func TestLeastGreatestPackedDateRejectsTimeFallback(t *testing.T) { + _, err := leastGreatestParsePackedDateBytes([]byte("1"), types.T_date.ToType(), nil, time.Local) + require.Error(t, err) +} + func TestLeastGreatestPackedDateUsesStatementStartForTime(t *testing.T) { proc := testutil.NewProcess(t) loc := time.FixedZone("UTC+8", 8*60*60) diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 4f626a121c6c7..f310c1445fc37 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -180,15 +180,14 @@ select greatest(cast('2020-01-01' as date), '2020-01-02') as greatest_date_varch ➤ greatest_date_varchar[12,-1,0] 𝄀 2020-01-02 select greatest(cast('2020-01-01' as date), 'not-a-date'); -invalid input: invalid datetime value not-a-date +invalid argument parsedate, bad value not-a-date select least(cast('2020-01-01' as date), 'not-a-date'); -invalid input: invalid datetime value not-a-date -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time; -➤ greatest_date_time[93,64,0] 𝄀 -2026-07-14 12:00:00 +invalid argument parsedate, bad value not-a-date +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as datetime) > cast('2020-01-01' as datetime) as greatest_date_time_uses_time; +➤ greatest_date_time_uses_time[-7,1,0] 𝄀 +1 select greatest(cast(2020 as year), cast('2020-01-01' as date), 1) as greatest_year_date_num; -➤ greatest_year_date_num[12,-1,0] 𝄀 -2026-07-14 +invalid argument parsedate, bad value 1 select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as blob))) as greatest_date_blob; ➤ greatest_date_blob[12,-1,0] 𝄀 323032302D30312D3032 @@ -280,9 +279,9 @@ select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00: select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), 20200103) as greatest_json_date_bigint; ➤ greatest_json_date_bigint[12,-1,0] 𝄀 2020-01-03 -select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_json_date_time; -➤ greatest_json_date_time[12,-1,0] 𝄀 -2026-07-14 +select cast(greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('12:00:00' as time)) as date) > cast('2020-01-02' as date) as greatest_json_date_time_uses_time; +➤ greatest_json_date_time_uses_time[-7,1,0] 𝄀 +1 select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), interval 1 day); invalid argument function greatest, bad value [JSON DATE INTERVAL] select greatest(cast('2020-01-01' as datetime), '2020-01-02') as greatest_datetime_varchar; @@ -291,9 +290,9 @@ select greatest(cast('2020-01-01' as datetime), '2020-01-02') as greatest_dateti select greatest(cast('2020-01-01' as date), cast('2020-01-01 12:00:00' as datetime)) as greatest_date_datetime; ➤ greatest_date_datetime[93,64,0] 𝄀 2020-01-01 12:00:00 -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time_format; -➤ greatest_date_time_format[93,64,0] 𝄀 -2026-07-14 12:00:00 +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as datetime) > cast('2020-01-01' as datetime) as greatest_date_time_format_uses_time; +➤ greatest_date_time_format_uses_time[-7,1,0] 𝄀 +1 select greatest(cast('2020-01-01' as date), cast('2020-01-02' as text)) as greatest_date_text; ➤ greatest_date_text[12,0,0] 𝄀 2020-01-02 @@ -312,15 +311,15 @@ select greatest(cast('2020-01-01' as date), cast(20200102 as bit(25))) as greate select greatest(cast(2020 as year), cast('10:00:00' as time)) as greatest_year_time; ➤ greatest_year_time[12,-1,0] 𝄀 2020 -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), '2020-01-02') as greatest_date_time_varchar; -➤ greatest_date_time_varchar[12,-1,0] 𝄀 -2026-07-14 -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), 20200102) as greatest_date_time_bigint; -➤ greatest_date_time_bigint[12,-1,0] 𝄀 -2026-07-14 -select hex(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), cast('2020-01-02' as blob))) as greatest_date_time_blob; -➤ greatest_date_time_blob[12,-1,0] 𝄀 -323032362D30372D3134 +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), '2020-01-02') as date) > cast('2020-01-02' as date) as greatest_date_time_varchar_uses_time; +➤ greatest_date_time_varchar_uses_time[-7,1,0] 𝄀 +1 +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), 20200102) as date) > cast('2020-01-02' as date) as greatest_date_time_bigint_uses_time; +➤ greatest_date_time_bigint_uses_time[-7,1,0] 𝄀 +1 +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), cast('2020-01-02' as blob)) as date) > cast('2020-01-02' as date) as greatest_date_time_blob_uses_time; +➤ greatest_date_time_blob_uses_time[-7,1,0] 𝄀 +1 select greatest(cast('b' as text), cast('a' as blob)) as greatest_text_blob; ➤ greatest_text_blob[12,0,0] 𝄀 b @@ -343,7 +342,7 @@ select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); invalid argument function greatest, bad value [UUID VARCHAR] select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; ➤ greatest_rowid_same_oid[12,0,0] 𝄀 -019f5eef-b733-705c-bde4-8ec6ae5317bf-0-0-0 +019f5f74-bf5b-79d6-94d0-0b64a15606a6-0-0-0 select greatest(__mo_rowid, 1) from greatest_least_supported_oid; invalid argument function greatest, bad value [ROWID BIGINT] select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; @@ -377,7 +376,7 @@ select least(e, 'b') from greatest_least_enum; ➤ least(e, b)[12,-1,0] 𝄀 a select greatest(e, d) from greatest_least_enum; -invalid input: invalid datetime value a +invalid argument parsedate, bad value a drop table if exists toll_transactions; drop table if exists mixed_num; drop table if exists uns; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index a0298318bb259..45b2aa18532e2 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -104,11 +104,12 @@ select greatest(cast('2020-01-01' as date), json_extract('"2020-01-02"', '$')) a select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast(2021 as year)) as greatest_json_date_year; -- Date-bearing temporal values compare as packed datetime values. Invalid --- temporal text returns MatrixOne's invalid-input error. +-- temporal text returns MatrixOne's invalid-input error. TIME uses the +-- statement date, so assert that it wins instead of snapshotting that date. select greatest(cast('2020-01-01' as date), '2020-01-02') as greatest_date_varchar; select greatest(cast('2020-01-01' as date), 'not-a-date'); select least(cast('2020-01-01' as date), 'not-a-date'); -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time; +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as datetime) > cast('2020-01-01' as datetime) as greatest_date_time_uses_time; select greatest(cast(2020 as year), cast('2020-01-01' as date), 1) as greatest_year_date_num; select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as blob))) as greatest_date_blob; select hex(greatest(cast('2020-01-01' as date), cast('2020-01-02' as varbinary))) as greatest_date_varbinary; @@ -156,23 +157,23 @@ select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), c select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00:00:00' as datetime)) as greatest_json_datetime; select greatest(json_extract('"2020-01-02 12:00:00"', '$'), cast('2020-01-01 00:00:00' as timestamp)) as greatest_json_timestamp; select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), 20200103) as greatest_json_date_bigint; -select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_json_date_time; +select cast(greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), cast('12:00:00' as time)) as date) > cast('2020-01-02' as date) as greatest_json_date_time_uses_time; select greatest(json_extract('"2020-01-02"', '$'), cast('2020-01-01' as date), interval 1 day); -- The date-bearing temporal matrix uses packed datetime comparison and chooses -- the documented result family from the non-temporal peer. select greatest(cast('2020-01-01' as datetime), '2020-01-02') as greatest_datetime_varchar; select greatest(cast('2020-01-01' as date), cast('2020-01-01 12:00:00' as datetime)) as greatest_date_datetime; -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as greatest_date_time_format; +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time)) as datetime) > cast('2020-01-01' as datetime) as greatest_date_time_format_uses_time; select greatest(cast('2020-01-01' as date), cast('2020-01-02' as text)) as greatest_date_text; select least(cast('10:00:00' as time), cast('09:00:00' as text)) as least_time_text; select greatest(cast('10:00:00' as time), cast('09:00:00' as blob)) as greatest_time_blob; select greatest(cast('2020-01-01' as date), 20200102) as greatest_date_numeric; select greatest(cast('2020-01-01' as date), cast(20200102 as bit(25))) as greatest_date_bit; select greatest(cast(2020 as year), cast('10:00:00' as time)) as greatest_year_time; -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), '2020-01-02') as greatest_date_time_varchar; -select greatest(cast('2020-01-01' as date), cast('12:00:00' as time), 20200102) as greatest_date_time_bigint; -select hex(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), cast('2020-01-02' as blob))) as greatest_date_time_blob; +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), '2020-01-02') as date) > cast('2020-01-02' as date) as greatest_date_time_varchar_uses_time; +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), 20200102) as date) > cast('2020-01-02' as date) as greatest_date_time_bigint_uses_time; +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00' as time), cast('2020-01-02' as blob)) as date) > cast('2020-01-02' as date) as greatest_date_time_blob_uses_time; -- String-family precedence is TEXT > CHAR/VARCHAR > BLOB > BINARY/VARBINARY. select greatest(cast('b' as text), cast('a' as blob)) as greatest_text_blob; From 63b9c332cad1a389f65588171e7e9038d43af125 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 15:54:57 +0800 Subject: [PATCH 05/14] update --- .../function/func_builtin_leastgreatest.go | 2 +- .../func_builtin_leastgreatest_test.go | 36 +++++++++++++++++++ .../function/greatest_least_numeric.result | 3 -- .../cases/function/greatest_least_numeric.sql | 4 ++- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index a22883ce460f8..fbf23f67e4598 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -966,7 +966,7 @@ func leastGreatestFnPackedDateResolved( } loc := time.Local - if proc != nil && proc.GetSessionInfo() != nil { + if proc != nil && proc.GetSessionInfo() != nil && proc.GetSessionInfo().TimeZone != nil { loc = proc.GetSessionInfo().TimeZone } diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 76e63ff3d1dfa..2bd15faa22b02 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -1047,6 +1047,42 @@ func TestLeastGreatestPackedDateRejectsTimeFallback(t *testing.T) { require.Error(t, err) } +func TestLeastGreatestPackedDateFallsBackToLocalTimezone(t *testing.T) { + proc := testutil.NewProcess(t) + proc.GetSessionInfo().TimeZone = nil + + date, err := types.ParseDateCast("2020-01-01") + require.NoError(t, err) + timestamp, err := types.ParseTimestamp(time.Local, "2020-01-02 12:34:56", 0) + require.NoError(t, err) + + t.Run("stored timestamp", func(t *testing.T) { + tc := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(types.T_date.ToType(), []types.Date{date}, nil), + NewFunctionTestInput(types.T_timestamp.ToType(), []types.Timestamp{timestamp}, nil), + }, + NewFunctionTestResult(types.T_datetime.ToType(), false, + []types.Datetime{timestamp.ToDatetime(time.Local)}, nil), + greatestTemporalFn) + ok, info := tc.Run() + require.True(t, ok, info) + }) + + t.Run("timestamp string peer", func(t *testing.T) { + tc := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(types.T_timestamp.ToType(), []types.Timestamp{timestamp}, nil), + NewFunctionTestInput(types.T_varchar.ToType(), []string{"2020-01-03 00:00:00"}, nil), + }, + NewFunctionTestResult(types.T_varchar.ToType(), false, + []string{"2020-01-03 00:00:00"}, nil), + greatestTemporalFn) + ok, info := tc.Run() + require.True(t, ok, info) + }) +} + func TestLeastGreatestPackedDateUsesStatementStartForTime(t *testing.T) { proc := testutil.NewProcess(t) loc := time.FixedZone("UTC+8", 8*60*60) diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index f310c1445fc37..6a1db926e7856 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -340,9 +340,6 @@ select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), cast('0000 00000000-0000-0000-0000-000000000002 select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); invalid argument function greatest, bad value [UUID VARCHAR] -select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; -➤ greatest_rowid_same_oid[12,0,0] 𝄀 -019f5f74-bf5b-79d6-94d0-0b64a15606a6-0-0-0 select greatest(__mo_rowid, 1) from greatest_least_supported_oid; invalid argument function greatest, bad value [ROWID BIGINT] select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 45b2aa18532e2..1918b222ec203 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -188,7 +188,9 @@ create table greatest_least_supported_oid (id int primary key, v32 vecf32(3), v6 insert into greatest_least_supported_oid values (1, '[1,2,3]', '[1,2,3]'); select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), cast('00000000-0000-0000-0000-000000000002' as uuid)) as greatest_uuid_same_oid; select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); -select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; +-- __mo_rowid is regenerated for every table creation, so its raw value cannot +-- be asserted in a static BVT result file. +-- select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; select greatest(__mo_rowid, 1) from greatest_least_supported_oid; select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; select greatest(v32, v64) from greatest_least_supported_oid; From 56b766edf90889a8897012f605fbdf9857c915fa Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 16:37:33 +0800 Subject: [PATCH 06/14] update --- .../func_builtin_leastgreatest_test.go | 41 +++++++++++++++++++ .../function/greatest_least_numeric.result | 3 ++ .../cases/function/greatest_least_numeric.sql | 6 +-- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 2bd15faa22b02..9cc209e0fff9c 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -920,6 +920,47 @@ func TestLeastGreatestPackedDateMaterializesNullRows(t *testing.T) { }) } +func TestLeastGreatestVarlenMaterializesNullRows(t *testing.T) { + proc := testutil.NewProcess(t) + varcharTyp := types.T_varchar.ToType() + + t.Run("ignore all rows", func(t *testing.T) { + fcTC := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(varcharTyp, []string{"a", "b", "c"}, nil), + NewFunctionTestInput(varcharTyp, []string{"d", "e", "f"}, nil), + }, + NewFunctionTestResult(varcharTyp, false, nil, nil), + greatestFn) + require.NoError(t, fcTC.result.PreExtendAndReset(fcTC.fnLength)) + require.NoError(t, fcTC.fn(fcTC.parameters, fcTC.result, fcTC.proc, fcTC.fnLength, &FunctionSelectList{AllNull: true})) + + resultVec := fcTC.result.GetResultVector() + require.Equal(t, fcTC.fnLength, resultVec.Length()) + for i := 0; i < fcTC.fnLength; i++ { + require.True(t, resultVec.IsNull(uint64(i))) + } + }) + + t.Run("constant null argument", func(t *testing.T) { + fcTC := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestInput(varcharTyp, []string{"a", "b", "c"}, nil), + NewFunctionTestConstInput(varcharTyp, []string{"x"}, []bool{true}), + }, + NewFunctionTestResult(varcharTyp, false, nil, nil), + greatestFn) + require.NoError(t, fcTC.result.PreExtendAndReset(fcTC.fnLength)) + require.NoError(t, fcTC.fn(fcTC.parameters, fcTC.result, fcTC.proc, fcTC.fnLength, nil)) + + resultVec := fcTC.result.GetResultVector() + require.Equal(t, fcTC.fnLength, resultVec.Length()) + for i := 0; i < fcTC.fnLength; i++ { + require.True(t, resultVec.IsNull(uint64(i))) + } + }) +} + func TestLeastGreatestYearNumericExecutor(t *testing.T) { proc := testutil.NewProcess(t) decimalType := types.New(types.T_decimal128, 10, 1) diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 6a1db926e7856..c06b4a57ea146 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -340,6 +340,9 @@ select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), cast('0000 00000000-0000-0000-0000-000000000002 select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); invalid argument function greatest, bad value [UUID VARCHAR] +select greatest(__mo_rowid, __mo_rowid) = __mo_rowid as greatest_rowid_same_oid from greatest_least_supported_oid; +➤ greatest_rowid_same_oid[-7,1,0] 𝄀 +1 select greatest(__mo_rowid, 1) from greatest_least_supported_oid; invalid argument function greatest, bad value [ROWID BIGINT] select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 1918b222ec203..dff4092192d9a 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -188,9 +188,9 @@ create table greatest_least_supported_oid (id int primary key, v32 vecf32(3), v6 insert into greatest_least_supported_oid values (1, '[1,2,3]', '[1,2,3]'); select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), cast('00000000-0000-0000-0000-000000000002' as uuid)) as greatest_uuid_same_oid; select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); --- __mo_rowid is regenerated for every table creation, so its raw value cannot --- be asserted in a static BVT result file. --- select greatest(__mo_rowid, __mo_rowid) as greatest_rowid_same_oid from greatest_least_supported_oid; +-- __mo_rowid is regenerated for every table creation. Assert its self-comparison +-- rather than snapshotting the physical value. +select greatest(__mo_rowid, __mo_rowid) = __mo_rowid as greatest_rowid_same_oid from greatest_least_supported_oid; select greatest(__mo_rowid, 1) from greatest_least_supported_oid; select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; select greatest(v32, v64) from greatest_least_supported_oid; From 2d9d41f9b2686fe31f7f4e1673592abbeea3c8f8 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 16:50:48 +0800 Subject: [PATCH 07/14] update --- .../distributed/cases/function/builtin.result | 938 +++++++++--------- 1 file changed, 474 insertions(+), 464 deletions(-) diff --git a/test/distributed/cases/function/builtin.result b/test/distributed/cases/function/builtin.result index 1fe15e1c59715..819313019ac3d 100755 --- a/test/distributed/cases/function/builtin.result +++ b/test/distributed/cases/function/builtin.result @@ -2,33 +2,33 @@ drop table if exists t1; create table t1(a int,b int); insert into t1 values(5,-2),(10,3),(100,0),(4,3),(6,-3); select power(a,b) from t1; -power(a, b) -0.04 -1000.0 -1.0 -64.0 +➤ power(a, b)[8,54,0] 𝄀 +0.04 𝄀 +1000.0 𝄀 +1.0 𝄀 +64.0 𝄀 0.004629629629629629 select power(a,2) as a1, power(b,2) as b1 from t1 where power(a,2) > power(b,2) order by a1 asc; -a1 b1 -16.0 9.0 -25.0 4.0 -36.0 9.0 -100.0 9.0 -10000.0 0.0 +➤ a1[8,54,0] ¦ b1[8,54,0] 𝄀 +16.0 ¦ 9.0 𝄀 +25.0 ¦ 4.0 𝄀 +36.0 ¦ 9.0 𝄀 +100.0 ¦ 9.0 𝄀 +10000.0 ¦ 0.0 drop table if exists t1; create table t1(a date,b datetime); insert into t1 values("2022-06-01","2022-07-01 00:00:00"); insert into t1 values("2022-12-31","2011-01-31 12:00:00"); select month(a),month(b) from t1; -month(a) month(b) -6 7 -12 1 +➤ month(a)[-6,8,0] ¦ month(b)[-6,8,0] 𝄀 +6 ¦ 7 𝄀 +12 ¦ 1 select * from t1 where month(a)>month(b); -a b -2022-12-31 2011-01-31 12:00:00 +➤ a[91,64,0] ¦ b[93,64,0] 𝄀 +2022-12-31 ¦ 2011-01-31 12:00:00 select * from t1 where month(a) between 1 and 6; -a b -2022-06-01 2022-07-01 00:00:00 +➤ a[91,64,0] ¦ b[93,64,0] 𝄀 +2022-06-01 ¦ 2022-07-01 00:00:00 drop table if exists t1; create table t1(a varchar(12),c char(30)); insert into t1 values('sdfad ','2022-02-02 22:22:22'); @@ -36,81 +36,81 @@ insert into t1 values(' sdfad ','2022-02-02 22:22:22'); insert into t1 values('adsf sdfad','2022-02-02 22:22:22'); insert into t1 values(' sdfad','2022-02-02 22:22:22'); select reverse(a),reverse(c) from t1; -reverse(a) reverse(c) - dafds 22:22:22 20-20-2202 - dafds 22:22:22 20-20-2202 -dafds fsda 22:22:22 20-20-2202 -dafds 22:22:22 20-20-2202 +➤ reverse(a)[12,-1,0] ¦ reverse(c)[12,-1,0] 𝄀 + dafds ¦ 22:22:22 20-20-2202 𝄀 + dafds ¦ 22:22:22 20-20-2202 𝄀 +dafds fsda ¦ 22:22:22 20-20-2202 𝄀 +dafds ¦ 22:22:22 20-20-2202 select a from t1 where reverse(a) like 'daf%'; -a -adsf sdfad +➤ a[12,-1,0] 𝄀 +adsf sdfad 𝄀 sdfad select reverse(a) reversea,reverse(reverse(a)) normala from t1; -reversea normala - dafds sdfad - dafds sdfad -dafds fsda adsf sdfad -dafds sdfad +➤ reversea[12,-1,0] ¦ normala[12,-1,0] 𝄀 + dafds ¦ sdfad 𝄀 + dafds ¦ sdfad 𝄀 +dafds fsda ¦ adsf sdfad 𝄀 +dafds ¦ sdfad drop table if exists t1; create table t1(a int,b float); insert into t1 values(0,0),(-15,-20),(-22,-12.5); insert into t1 values(0,360),(30,390),(90,450),(180,270),(180,180); select acos(a*pi()/180) as acosa,acos(b*pi()/180) acosb from t1; -acosa acosb -1.5707963267948966 1.5707963267948966 -1.8356824738191324 1.927370391646567 -1.9648910192076245 1.7907312931992256 -1.5707963267948966 null -1.0197267436954502 null -null null -null null -null null +➤ acosa[8,54,0] ¦ acosb[8,54,0] 𝄀 +1.5707963267948966 ¦ 1.5707963267948966 𝄀 +1.8356824738191324 ¦ 1.927370391646567 𝄀 +1.9648910192076245 ¦ 1.7907312931992256 𝄀 +1.5707963267948966 ¦ null 𝄀 +1.0197267436954502 ¦ null 𝄀 +null ¦ null 𝄀 +null ¦ null 𝄀 +null ¦ null select acos(a*pi()/180)*acos(b*pi()/180) as acosab,acos(acos(a*pi()/180)) as c from t1; -acosab c -2.4674011002723395 null -3.5380400485035204 null -3.518591835821214 null -null null -null null -null null -null null -null null +➤ acosab[8,54,0] ¦ c[8,54,0] 𝄀 +2.4674011002723395 ¦ null 𝄀 +3.5380400485035204 ¦ null 𝄀 +3.518591835821214 ¦ null 𝄀 +null ¦ null 𝄀 +null ¦ null 𝄀 +null ¦ null 𝄀 +null ¦ null 𝄀 +null ¦ null select b from t1 where acos(a*pi()/180)<=acos(b*pi()/180) order by a; -b --20.0 +➤ b[7,24,0] 𝄀 +-20.0 𝄀 0.0 drop table if exists t1; create table t1(a int,b float); insert into t1 values(0,0),(-15,-20),(-22,-12.5); insert into t1 values(0,360),(30,390),(90,450),(180,270),(180,180); select atan(a*pi()/180) as atana,atan(b*pi()/180) atanb from t1; -atana atanb -0.0 0.0 --0.25605276998075555 -0.3358423725664079 --0.36661361829326644 -0.2148004503403853 -0.0 1.4129651365067377 -0.48234790710102493 1.4249275378445119 -1.0038848218538872 1.4441537892065186 -1.2626272556789115 1.3616916829711634 -1.2626272556789115 1.2626272556789115 +➤ atana[8,54,0] ¦ atanb[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +-0.25605276998075555 ¦ -0.3358423725664079 𝄀 +-0.36661361829326644 ¦ -0.2148004503403853 𝄀 +0.0 ¦ 1.4129651365067377 𝄀 +0.48234790710102493 ¦ 1.4249275378445119 𝄀 +1.0038848218538872 ¦ 1.4441537892065186 𝄀 +1.2626272556789115 ¦ 1.3616916829711634 𝄀 +1.2626272556789115 ¦ 1.2626272556789115 select atan(a*pi()/180)*atan(b*pi()/180) as atanab,atan(atan(a*pi()/180)) as c from t1; -atanab c -0.0 0.0 -0.08599336977253766 -0.25066722482387166 -0.07874877031031176 -0.35139803165815964 -0.0 0.0 -0.6873108156499168 0.44942647356532794 -1.449764069407202 0.7873368062499201 -1.7193090327506784 0.900952887864509 -1.5942275867832594 0.900952887864509 +➤ atanab[8,54,0] ¦ c[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +0.08599336977253766 ¦ -0.25066722482387166 𝄀 +0.07874877031031176 ¦ -0.35139803165815964 𝄀 +0.0 ¦ 0.0 𝄀 +0.6873108156499168 ¦ 0.44942647356532794 𝄀 +1.449764069407202 ¦ 0.7873368062499201 𝄀 +1.7193090327506784 ¦ 0.900952887864509 𝄀 +1.5942275867832594 ¦ 0.900952887864509 select b from t1 where atan(a*pi()/180)<=atan(b*pi()/180) order by a; -b --12.5 -0.0 -360.0 -390.0 -450.0 -270.0 +➤ b[7,24,0] 𝄀 +-12.5 𝄀 +0.0 𝄀 +360.0 𝄀 +390.0 𝄀 +450.0 𝄀 +270.0 𝄀 180.0 drop table if exists t1; CREATE TABLE t1( @@ -132,14 +132,14 @@ VALUES ('Sudip Majhi', 'ABC Corp.', 'Delhi', '2018-10-30', 600000), ('Sanjoy Kohli', 'XYZ Digital', 'Delhi', '2019-04-18', 450000); SELECT Working_At, BIT_AND(Annual_Income) AS BITORINCOME FROM t1 group by Working_At; -Working_At BITORINCOME -XYZ Digital 262144 -ABC Corp. 65792 -PQR Soln. 4096 +➤ Working_At[12,-1,0] ¦ BITORINCOME[-5,64,0] 𝄀 +XYZ Digital ¦ 262144 𝄀 +ABC Corp. ¦ 65792 𝄀 +PQR Soln. ¦ 4096 SELECT Work_Location, BIT_AND(Annual_Income) AS BITORINCOME FROM t1 Group By Work_Location; -Work_Location BITORINCOME -Kolkata 262144 -Delhi 0 +➤ Work_Location[12,-1,0] ¦ BITORINCOME[-5,64,0] 𝄀 +Kolkata ¦ 262144 𝄀 +Delhi ¦ 0 drop table if exists t1; CREATE TABLE t1( Employee_Name VARCHAR(100) NOT NULL, @@ -160,14 +160,14 @@ VALUES ('Sudip Majhi', 'ABC Corp.', 'Delhi', '2018-10-30', 600000), ('Sanjoy Kohli', 'XYZ Digital', 'Delhi', '2019-04-18', 450000); SELECT Work_Location, BIT_AND(Annual_Income) AS BITORINCOME FROM t1 Group By Work_Location; -Work_Location BITORINCOME -Kolkata 262144 -Delhi 0 +➤ Work_Location[12,-1,0] ¦ BITORINCOME[-5,64,0] 𝄀 +Kolkata ¦ 262144 𝄀 +Delhi ¦ 0 SELECT Working_At, BIT_AND(Annual_Income) AS BITORINCOME FROM t1 group by Working_At; -Working_At BITORINCOME -XYZ Digital 262144 -ABC Corp. 65792 -PQR Soln. 4096 +➤ Working_At[12,-1,0] ¦ BITORINCOME[-5,64,0] 𝄀 +XYZ Digital ¦ 262144 𝄀 +ABC Corp. ¦ 65792 𝄀 +PQR Soln. ¦ 4096 drop table if exists t1; CREATE TABLE t1( Employee_Name VARCHAR(100) NOT NULL, @@ -188,38 +188,38 @@ VALUES ('Sudip Majhi', 'ABC Corp.', 'Delhi', '2018-10-30', 600000), ('Sanjoy Kohli', 'XYZ Digital', 'Delhi', '2019-04-18', 450000); SELECT Work_Location, BIT_XOR(Annual_Income) AS BITORINCOME FROM t1 Group By Work_Location; -Work_Location BITORINCOME -Kolkata 350624 -Delhi 912976 +➤ Work_Location[12,-1,0] ¦ BITORINCOME[-5,64,0] 𝄀 +Kolkata ¦ 350624 𝄀 +Delhi ¦ 912976 SELECT Working_At, BIT_XOR(Annual_Income) AS BITORINCOME FROM t1 group by Working_At; -Working_At BITORINCOME -XYZ Digital 94816 -ABC Corp. 774608 -PQR Soln. 136256 +➤ Working_At[12,-1,0] ¦ BITORINCOME[-5,64,0] 𝄀 +XYZ Digital ¦ 94816 𝄀 +ABC Corp. ¦ 774608 𝄀 +PQR Soln. ¦ 136256 drop table if exists t1; create table t1(a int,b float); insert into t1 values(0,0); insert into t1 values(0,360),(30,390),(90,450),(180,270),(180,180); select cos(a),cos(b) from t1; -cos(a) cos(b) -1.0 1.0 -1.0 -0.2836910914865273 -0.15425144988758405 0.9036792973912307 --0.4480736161291701 -0.7301529641805058 --0.5984600690578581 0.9843819506325049 --0.5984600690578581 -0.5984600690578581 +➤ cos(a)[8,54,0] ¦ cos(b)[8,54,0] 𝄀 +1.0 ¦ 1.0 𝄀 +1.0 ¦ -0.2836910914865273 𝄀 +0.15425144988758405 ¦ 0.9036792973912307 𝄀 +-0.4480736161291701 ¦ -0.7301529641805058 𝄀 +-0.5984600690578581 ¦ 0.9843819506325049 𝄀 +-0.5984600690578581 ¦ -0.5984600690578581 select cos(a)*cos(b),cos(cos(a)) as c from t1; -cos(a) * cos(b) c -1.0 0.5403023058681398 --0.2836910914865273 0.5403023058681398 -0.13939384185599057 0.9881268151992377 -0.32716227898779165 0.9012833416649748 --0.5891132901548379 0.8262041463870422 -0.35815445425673625 0.8262041463870422 +➤ cos(a) * cos(b)[8,54,0] ¦ c[8,54,0] 𝄀 +1.0 ¦ 0.5403023058681398 𝄀 +-0.2836910914865273 ¦ 0.5403023058681398 𝄀 +0.13939384185599057 ¦ 0.9881268151992377 𝄀 +0.32716227898779165 ¦ 0.9012833416649748 𝄀 +-0.5891132901548379 ¦ 0.8262041463870422 𝄀 +0.35815445425673625 ¦ 0.8262041463870422 select distinct a from t1 where cos(a)<=cos(b) order by a desc; -a -180 -30 +➤ a[4,32,0] 𝄀 +180 𝄀 +30 𝄀 0 drop table if exists t1_cot_safe; drop table if exists t1_cot_nested_safe; @@ -232,26 +232,26 @@ insert into t1_cot_safe select * from t1 where a <> 0 and b <> 0; create table t1_cot_nested_safe(a int,b float); insert into t1_cot_nested_safe select * from t1_cot_safe where a <> 90; select cot(a*pi()/180) as cota,cot(b*pi()/180) cotb from t1_cot_safe; -cota cotb --3.732050807568879 -2.7474774194546225 --2.4750868534162955 -4.510708503662059 -1.7320508075688774 1.7320508075688807 -0.0 2.449293598294703E-16 --1.6331239353195392E16 1.2246467991473515E-16 --1.6331239353195392E16 -1.6331239353195392E16 +➤ cota[8,54,0] ¦ cotb[8,54,0] 𝄀 +-3.732050807568879 ¦ -2.7474774194546225 𝄀 +-2.4750868534162955 ¦ -4.510708503662059 𝄀 +1.7320508075688774 ¦ 1.7320508075688807 𝄀 +0.0 ¦ 2.449293598294703E-16 𝄀 +-1.6331239353195392E16 ¦ 1.2246467991473515E-16 𝄀 +-1.6331239353195392E16 ¦ -1.6331239353195392E16 select cot(a*pi()/180)*cot(b*pi()/180) as cotab,cot(cot(a*pi()/180)) as c from t1_cot_nested_safe; -cotab c -10.253725322052883 -1.492048732224759 -11.164395317007052 1.2713225260580656 -3.000000000000006 -0.1626668736751883 --2.0 0.1507701967970538 -2.6670937881135786E32 0.1507701967970538 +➤ cotab[8,54,0] ¦ c[8,54,0] 𝄀 +10.253725322052883 ¦ -1.492048732224759 𝄀 +11.164395317007052 ¦ 1.2713225260580656 𝄀 +3.000000000000006 ¦ -0.1626668736751883 𝄀 +-2.0 ¦ 0.1507701967970538 𝄀 +2.6670937881135786E32 ¦ 0.1507701967970538 select b from t1_cot_safe where cot(a*pi()/180)<=cot(b*pi()/180) order by a; -b --20.0 -390.0 -450.0 -270.0 +➤ b[7,24,0] 𝄀 +-20.0 𝄀 +390.0 𝄀 +450.0 𝄀 +270.0 𝄀 180.0 select cot(0); Data truncation: data out of range: data type float64, DOUBLE value is out of range in 'cot(0)' @@ -261,8 +261,8 @@ drop table if exists t1; create table t1(a date, b datetime,c varchar(30)); insert into t1 values('20220101','2022-01-01 01:01:01','2022-13-13 01:01:01'); select * from t1; -a b c -2022-01-01 2022-01-01 01:01:01 2022-13-13 01:01:01 +➤ a[91,64,0] ¦ b[93,64,0] ¦ c[12,-1,0] 𝄀 +2022-01-01 ¦ 2022-01-01 01:01:01 ¦ 2022-13-13 01:01:01 drop table if exists t1; create table t1(a date, b datetime,c varchar(30)); insert into t1 values('2022-01-01','2022-01-01 01:01:01','2022-01-01 01:01:01'); @@ -271,21 +271,21 @@ insert into t1 values('2022-01-02','2022-01-02 23:01:01','2022-01-01 23:01:01'); insert into t1 values('2021-12-31','2021-12-30 23:59:59','2021-12-30 23:59:59'); insert into t1 values('2022-06-30','2021-12-30 23:59:59','2021-12-30 23:59:59'); select distinct dayofyear(a) as dya from t1; -dya -1 -2 -365 +➤ dya[5,16,0] 𝄀 +1 𝄀 +2 𝄀 +365 𝄀 181 select * from t1 where dayofyear(a)>120; -a b c -2021-12-31 2021-12-30 23:59:59 2021-12-30 23:59:59 -2022-06-30 2021-12-30 23:59:59 2021-12-30 23:59:59 +➤ a[91,64,0] ¦ b[93,64,0] ¦ c[12,-1,0] 𝄀 +2021-12-31 ¦ 2021-12-30 23:59:59 ¦ 2021-12-30 23:59:59 𝄀 +2022-06-30 ¦ 2021-12-30 23:59:59 ¦ 2021-12-30 23:59:59 select * from t1 where dayofyear(a) between 1 and 184; -a b c -2022-01-01 2022-01-01 01:01:01 2022-01-01 01:01:01 -2022-01-01 2022-01-01 01:01:01 2022-01-01 01:01:01 -2022-01-02 2022-01-02 23:01:01 2022-01-01 23:01:01 -2022-06-30 2021-12-30 23:59:59 2021-12-30 23:59:59 +➤ a[91,64,0] ¦ b[93,64,0] ¦ c[12,-1,0] 𝄀 +2022-01-01 ¦ 2022-01-01 01:01:01 ¦ 2022-01-01 01:01:01 𝄀 +2022-01-01 ¦ 2022-01-01 01:01:01 ¦ 2022-01-01 01:01:01 𝄀 +2022-01-02 ¦ 2022-01-02 23:01:01 ¦ 2022-01-01 23:01:01 𝄀 +2022-06-30 ¦ 2021-12-30 23:59:59 ¦ 2021-12-30 23:59:59 drop table if exists t1; CREATE TABLE t1(a INT,b VARCHAR(100),c CHAR(20)); INSERT INTO t1 @@ -297,17 +297,17 @@ VALUES (5,'Riya Jain', 'IX'), (6,'Tapan Samanta', 'XI'); select a,endswith(b,'a') from t1; -a endswith(b, a) -1 0 -2 1 -3 1 -4 0 -5 0 -6 1 +➤ a[4,32,0] ¦ endswith(b, a)[-7,1,0] 𝄀 +1 ¦ 0 𝄀 +2 ¦ 1 𝄀 +3 ¦ 1 𝄀 +4 ¦ 0 𝄀 +5 ¦ 0 𝄀 +6 ¦ 1 select a,b,c from t1 where endswith(b,'a') and endswith(c,'I'); -a b c -3 Aniket Sharma XI -6 Tapan Samanta XI +➤ a[4,32,0] ¦ b[12,-1,0] ¦ c[1,20,0] 𝄀 +3 ¦ Aniket Sharma ¦ XI 𝄀 +6 ¦ Tapan Samanta ¦ XI drop table if exists t1; CREATE TABLE t1(Student_id INT,Student_name VARCHAR(100),Student_Class CHAR(20)); INSERT INTO t1 @@ -319,37 +319,37 @@ VALUES (5,'Riya Jain', 'IX'), (6,'Tapan Samanta', 'X'); SELECT Student_id, Student_name,LPAD(Student_Class, 10, ' _') AS LeftPaddedString FROM t1; -Student_id Student_name LeftPaddedString -1 Ananya Majumdar _ _ _ _IX -2 Anushka Samanta _ _ _ _ X -3 Aniket Sharma _ _ _ _XI -4 Anik Das _ _ _ _ X -5 Riya Jain _ _ _ _IX -6 Tapan Samanta _ _ _ _ X +➤ Student_id[4,32,0] ¦ Student_name[12,-1,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ Ananya Majumdar ¦ _ _ _ _IX 𝄀 +2 ¦ Anushka Samanta ¦ _ _ _ _ X 𝄀 +3 ¦ Aniket Sharma ¦ _ _ _ _XI 𝄀 +4 ¦ Anik Das ¦ _ _ _ _ X 𝄀 +5 ¦ Riya Jain ¦ _ _ _ _IX 𝄀 +6 ¦ Tapan Samanta ¦ _ _ _ _ X SELECT Student_id, lpad(Student_name,4,'new') AS LeftPaddedString FROM t1; -Student_id LeftPaddedString -1 Anan -2 Anus -3 Anik -4 Anik -5 Riya -6 Tapa +➤ Student_id[4,32,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ Anan 𝄀 +2 ¦ Anus 𝄀 +3 ¦ Anik 𝄀 +4 ¦ Anik 𝄀 +5 ¦ Riya 𝄀 +6 ¦ Tapa SELECT Student_id, lpad(Student_name,-4,'new') AS LeftPaddedString FROM t1; -Student_id LeftPaddedString -1 null -2 null -3 null -4 null -5 null -6 null +➤ Student_id[4,32,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ null 𝄀 +2 ¦ null 𝄀 +3 ¦ null 𝄀 +4 ¦ null 𝄀 +5 ¦ null 𝄀 +6 ¦ null SELECT Student_id, lpad(Student_name,0,'new') AS LeftPaddedString FROM t1; -Student_id LeftPaddedString -1 -2 -3 -4 -5 -6 +➤ Student_id[4,32,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ 𝄀 +2 ¦ 𝄀 +3 ¦ 𝄀 +4 ¦ 𝄀 +5 ¦ 𝄀 +6 ¦ drop table if exists t1; CREATE TABLE t1(Student_id INT,Student_name VARCHAR(100),Student_Class CHAR(20)); INSERT INTO t1 @@ -361,37 +361,37 @@ VALUES (5,'Riya Jain', 'IX'), (6,'Tapan Samanta', 'X'); SELECT Student_id, Student_name,RPAD(Student_Class, 10, ' _') AS LeftPaddedString FROM t1; -Student_id Student_name LeftPaddedString -1 Ananya Majumdar IX _ _ _ _ -2 Anushka Samanta X _ _ _ _ -3 Aniket Sharma XI _ _ _ _ -4 Anik Das X _ _ _ _ -5 Riya Jain IX _ _ _ _ -6 Tapan Samanta X _ _ _ _ +➤ Student_id[4,32,0] ¦ Student_name[12,-1,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ Ananya Majumdar ¦ IX _ _ _ _ 𝄀 +2 ¦ Anushka Samanta ¦ X _ _ _ _ 𝄀 +3 ¦ Aniket Sharma ¦ XI _ _ _ _ 𝄀 +4 ¦ Anik Das ¦ X _ _ _ _ 𝄀 +5 ¦ Riya Jain ¦ IX _ _ _ _ 𝄀 +6 ¦ Tapan Samanta ¦ X _ _ _ _ SELECT Student_id, rpad(Student_name,4,'new') AS LeftPaddedString FROM t1; -Student_id LeftPaddedString -1 Anan -2 Anus -3 Anik -4 Anik -5 Riya -6 Tapa +➤ Student_id[4,32,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ Anan 𝄀 +2 ¦ Anus 𝄀 +3 ¦ Anik 𝄀 +4 ¦ Anik 𝄀 +5 ¦ Riya 𝄀 +6 ¦ Tapa SELECT Student_id, rpad(Student_name,-4,'new') AS LeftPaddedString FROM t1; -Student_id LeftPaddedString -1 null -2 null -3 null -4 null -5 null -6 null +➤ Student_id[4,32,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ null 𝄀 +2 ¦ null 𝄀 +3 ¦ null 𝄀 +4 ¦ null 𝄀 +5 ¦ null 𝄀 +6 ¦ null SELECT Student_id, rpad(Student_name,0,'new') AS LeftPaddedString FROM t1; -Student_id LeftPaddedString -1 -2 -3 -4 -5 -6 +➤ Student_id[4,32,0] ¦ LeftPaddedString[12,-1,0] 𝄀 +1 ¦ 𝄀 +2 ¦ 𝄀 +3 ¦ 𝄀 +4 ¦ 𝄀 +5 ¦ 𝄀 +6 ¦ drop table if exists t1; CREATE TABLE t1 ( @@ -411,77 +411,77 @@ VALUES (' Ankana Jana', '2018-08-17'), (' Shreya Ghosh', '2020-09-10') ; SELECT LTRIM( Employee_name) LTrimName,RTRIM(Employee_name) AS RTrimName FROM t1 order by RTrimName desc; -LTrimName RTrimName -Shreya Ghosh Shreya Ghosh -Riya Jain Riya Jain -Deepak Sharma Deepak Sharma -Anushka Samanta Anushka Samanta -Ankana Jana Ankana Jana -Aniket Sharma Aniket Sharma -Anik Das Anik Das -Tapan Samanta Tapan Samanta -Ananya Majumdar Ananya Majumdar +➤ LTrimName[12,-1,0] ¦ RTrimName[12,-1,0] 𝄀 +Shreya Ghosh ¦ Shreya Ghosh 𝄀 +Riya Jain ¦ Riya Jain 𝄀 +Deepak Sharma ¦ Deepak Sharma 𝄀 +Anushka Samanta ¦ Anushka Samanta 𝄀 +Ankana Jana ¦ Ankana Jana 𝄀 +Aniket Sharma ¦ Aniket Sharma 𝄀 +Anik Das ¦ Anik Das 𝄀 +Tapan Samanta ¦ Tapan Samanta 𝄀 +Ananya Majumdar ¦ Ananya Majumdar SELECT LTRIM(RTRIM(Employee_name)) as TrimName from t1 where Employee_name like '%Ani%' order by TrimName asc; -TrimName -Anik Das +➤ TrimName[12,-1,0] 𝄀 +Anik Das 𝄀 Aniket Sharma drop table if exists t1; create table t1(a int,b float); insert into t1 values(0,0); insert into t1 values(0,360),(30,390),(90,450),(180,270),(180,180); select sin(a),sin(b) from t1; -sin(a) sin(b) -0.0 0.0 -0.0 0.9589157234143065 --0.9880316240928618 0.42820991051876856 -0.893996663600558 -0.6832837250355235 --0.8011526357338306 -0.17604594647121138 --0.8011526357338306 -0.8011526357338306 +➤ sin(a)[8,54,0] ¦ sin(b)[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +0.0 ¦ 0.9589157234143065 𝄀 +-0.9880316240928618 ¦ 0.42820991051876856 𝄀 +0.893996663600558 ¦ -0.6832837250355235 𝄀 +-0.8011526357338306 ¦ -0.17604594647121138 𝄀 +-0.8011526357338306 ¦ -0.8011526357338306 select sin(a)*sin(b),sin(sin(a)) as c from t1; -sin(a) * sin(b) c -0.0 0.0 -0.0 0.0 --0.42308493334251795 -0.8349443318035336 --0.610853370474319 0.77958108276669 -0.14103967402566783 -0.7181586632423703 -0.6418455457432638 -0.7181586632423703 +➤ sin(a) * sin(b)[8,54,0] ¦ c[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +0.0 ¦ 0.0 𝄀 +-0.42308493334251795 ¦ -0.8349443318035336 𝄀 +-0.610853370474319 ¦ 0.77958108276669 𝄀 +0.14103967402566783 ¦ -0.7181586632423703 𝄀 +0.6418455457432638 ¦ -0.7181586632423703 select distinct a from t1 where sin(a)<=sin(b) order by a desc; -a -180 -30 +➤ a[4,32,0] 𝄀 +180 𝄀 +30 𝄀 0 drop table if exists t1; create table t1(a int,b float); insert into t1 values(0,0),(-15,-20),(-22,-12.5); insert into t1 values(0,360),(30,390),(90,450),(180,270),(180,180); select sinh(a*pi()/180) as sinha,sinh(b*pi()/180) sinhb from t1; -sinha sinhb -0.0 0.0 --0.2648002276022707 -0.3561979324000117 --0.3934773854637668 -0.219900936381245 -0.0 267.74489404101644 -0.5478534738880397 451.9789818592585 -2.3012989023072947 1287.985054197183 -11.548739357257748 55.65439759941754 -11.548739357257748 11.548739357257748 +➤ sinha[8,54,0] ¦ sinhb[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +-0.2648002276022707 ¦ -0.3561979324000117 𝄀 +-0.3934773854637668 ¦ -0.219900936381245 𝄀 +0.0 ¦ 267.74489404101644 𝄀 +0.5478534738880397 ¦ 451.9789818592585 𝄀 +2.3012989023072947 ¦ 1287.985054197183 𝄀 +11.548739357257748 ¦ 55.65439759941754 𝄀 +11.548739357257748 ¦ 11.548739357257748 select sinh(a*pi()/180)*sinh(b*pi()/180) as sinhab,sinh(sinh(a*pi()/180)) as c from t1; -sinhab c -0.0 0.0 -0.09432129357098132 -0.26790569019819105 -0.0865260455083264 -0.40370959509281085 -0.0 0.0 -247.61825533597406 0.57567347843079 -2964.038591412179 4.943508829600678 -642.7381319608645 51823.146734897804 -133.37338074187412 51823.146734897804 +➤ sinhab[8,54,0] ¦ c[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +0.09432129357098132 ¦ -0.26790569019819105 𝄀 +0.0865260455083264 ¦ -0.40370959509281085 𝄀 +0.0 ¦ 0.0 𝄀 +247.61825533597406 ¦ 0.57567347843079 𝄀 +2964.038591412179 ¦ 4.943508829600678 𝄀 +642.7381319608645 ¦ 51823.146734897804 𝄀 +133.37338074187412 ¦ 51823.146734897804 select b from t1 where sinh(a*pi()/180)<=sinh(b*pi()/180) order by a; -b --12.5 -0.0 -360.0 -390.0 -450.0 -270.0 +➤ b[7,24,0] 𝄀 +-12.5 𝄀 +0.0 𝄀 +360.0 𝄀 +390.0 𝄀 +450.0 𝄀 +270.0 𝄀 180.0 drop table if exists t1; CREATE TABLE t1 @@ -504,8 +504,8 @@ VALUES INSERT INTO t1 (Employee_name, Joining_Date ) values(' ','2014-12-01'); select * from t1 where Employee_name=space(5); -employee_name joining_date - 2014-12-01 +➤ employee_name[12,-1,0] ¦ joining_date[91,64,0] 𝄀 + ¦ 2014-12-01 drop table if exists t1; CREATE TABLE t1(a INT,b VARCHAR(100),c CHAR(20)); INSERT INTO t1 @@ -517,54 +517,54 @@ VALUES (5,'Riya Jain', 'IX'), (6,'Tapan Samanta', 'X'); select a,startswith(b,'An') from t1; -a startswith(b, An) -1 1 -2 1 -3 1 -4 1 -5 0 -6 0 +➤ a[4,32,0] ¦ startswith(b, An)[-7,1,0] 𝄀 +1 ¦ 1 𝄀 +2 ¦ 1 𝄀 +3 ¦ 1 𝄀 +4 ¦ 1 𝄀 +5 ¦ 0 𝄀 +6 ¦ 0 select a,b,c from t1 where startswith(b,'An') and startswith(c,'I'); -a b c -1 Ananya Majumdar IX +➤ a[4,32,0] ¦ b[12,-1,0] ¦ c[1,20,0] 𝄀 +1 ¦ Ananya Majumdar ¦ IX drop table if exists t1; CREATE TABLE t1(PlayerName VARCHAR(100) NOT NULL,RunScored INT NOT NULL,WicketsTaken INT NOT NULL); INSERT INTO t1 VALUES('KL Rahul', 52, 0 ),('Hardik Pandya', 30, 1 ),('Ravindra Jadeja', 18, 2 ),('Washington Sundar', 10, 1),('D Chahar', 11, 2 ), ('Mitchell Starc', 0, 3); SELECT STDDEV_POP(RunScored) as Pop_Standard_Deviation FROM t1; -Pop_Standard_Deviation +➤ Pop_Standard_Deviation[8,54,0] 𝄀 16.876183086099637 SELECT STDDEV_POP(WicketsTaken) as Pop_Std_Dev_Wickets FROM t1; -Pop_Std_Dev_Wickets +➤ Pop_Std_Dev_Wickets[8,54,0] 𝄀 0.957427107756338 drop table if exists t1; create table t1(a int,b float); insert into t1 values(0,0),(-15,-20),(-22,-12.5); insert into t1 values(0,360),(30,390),(90,450),(180,270),(180,180); select tan(a*pi()/180) as tana,tan(b*pi()/180) tanb from t1; -tana tanb -0.0 0.0 --0.2679491924311227 -0.36397023426620234 --0.4040262258351568 -0.2216946626429399 -0.0 -2.449293598294703E-16 -0.5773502691896257 0.5773502691896246 -1.6331239353195392E16 3.2662478706390785E15 --1.2246467991473515E-16 5.443746451065131E15 --1.2246467991473515E-16 -1.2246467991473515E-16 +➤ tana[8,54,0] ¦ tanb[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +-0.2679491924311227 ¦ -0.36397023426620234 𝄀 +-0.4040262258351568 ¦ -0.2216946626429399 𝄀 +0.0 ¦ -2.449293598294703E-16 𝄀 +0.5773502691896257 ¦ 0.5773502691896246 𝄀 +1.6331239353195392E16 ¦ 3.2662478706390785E15 𝄀 +-1.2246467991473515E-16 ¦ 5.443746451065131E15 𝄀 +-1.2246467991473515E-16 ¦ -1.2246467991473515E-16 select tan(a*pi()/180)*tan(b*pi()/180) as tanab,tan(a*pi()/180)+tan(b*pi()/180) as c from t1; -tanab c -0.0 0.0 -0.09752553034059545 -0.631919426697325 -0.08957045783542533 -0.6257208884780967 --0.0 -2.449293598294703E-16 -0.33333333333333265 1.1547005383792504 -5.334187576227157E31 1.959748722383447E16 --0.6666666666666667 5.443746451065131E15 -1.4997597826618535E-32 -2.449293598294703E-16 +➤ tanab[8,54,0] ¦ c[8,54,0] 𝄀 +0.0 ¦ 0.0 𝄀 +0.09752553034059545 ¦ -0.631919426697325 𝄀 +0.08957045783542533 ¦ -0.6257208884780967 𝄀 +-0.0 ¦ -2.449293598294703E-16 𝄀 +0.33333333333333265 ¦ 1.1547005383792504 𝄀 +5.334187576227157E31 ¦ 1.9597487223834472E16 𝄀 +-0.6666666666666667 ¦ 5.443746451065131E15 𝄀 +1.4997597826618535E-32 ¦ -2.449293598294703E-16 select b from t1 where tan(a*pi()/180)<=tan(b*pi()/180) order by a; -b --12.5 -0.0 -270.0 +➤ b[7,24,0] 𝄀 +-12.5 𝄀 +0.0 𝄀 +270.0 𝄀 180.0 drop table if exists t1; create table t1(a date,b datetime); @@ -572,34 +572,34 @@ insert into t1 values("2022-06-01","2022-07-01 00:00:00"); insert into t1 values("2022-12-31","2011-01-31 12:00:00"); insert into t1 values("2022-06-12","2022-07-01 00:00:00"); select a,weekday(a),b,weekday(b) from t1; -a weekday(a) b weekday(b) -2022-06-01 2 2022-07-01 00:00:00 4 -2022-12-31 5 2011-01-31 12:00:00 0 -2022-06-12 6 2022-07-01 00:00:00 4 +➤ a[91,64,0] ¦ weekday(a)[-5,64,0] ¦ b[93,64,0] ¦ weekday(b)[-5,64,0] 𝄀 +2022-06-01 ¦ 2 ¦ 2022-07-01 00:00:00 ¦ 4 𝄀 +2022-12-31 ¦ 5 ¦ 2011-01-31 12:00:00 ¦ 0 𝄀 +2022-06-12 ¦ 6 ¦ 2022-07-01 00:00:00 ¦ 4 select * from t1 where weekday(a)>weekday(b); -a b -2022-12-31 2011-01-31 12:00:00 -2022-06-12 2022-07-01 00:00:00 +➤ a[91,64,0] ¦ b[93,64,0] 𝄀 +2022-12-31 ¦ 2011-01-31 12:00:00 𝄀 +2022-06-12 ¦ 2022-07-01 00:00:00 select * from t1 where weekday(a) between 0 and 4; -a b -2022-06-01 2022-07-01 00:00:00 +➤ a[91,64,0] ¦ b[93,64,0] 𝄀 +2022-06-01 ¦ 2022-07-01 00:00:00 drop table if exists t1; create table t1(a date,b datetime); insert into t1 values("2022-06-01","2022-07-01 00:00:00"); insert into t1 values("2022-12-31","2011-01-31 12:00:00"); insert into t1 values("2022-06-12","2022-07-01 00:00:00"); select a,weekday(a),b,weekday(b) from t1; -a weekday(a) b weekday(b) -2022-06-01 2 2022-07-01 00:00:00 4 -2022-12-31 5 2011-01-31 12:00:00 0 -2022-06-12 6 2022-07-01 00:00:00 4 +➤ a[91,64,0] ¦ weekday(a)[-5,64,0] ¦ b[93,64,0] ¦ weekday(b)[-5,64,0] 𝄀 +2022-06-01 ¦ 2 ¦ 2022-07-01 00:00:00 ¦ 4 𝄀 +2022-12-31 ¦ 5 ¦ 2011-01-31 12:00:00 ¦ 0 𝄀 +2022-06-12 ¦ 6 ¦ 2022-07-01 00:00:00 ¦ 4 select * from t1 where weekday(a)>weekday(b); -a b -2022-12-31 2011-01-31 12:00:00 -2022-06-12 2022-07-01 00:00:00 +➤ a[91,64,0] ¦ b[93,64,0] 𝄀 +2022-12-31 ¦ 2011-01-31 12:00:00 𝄀 +2022-06-12 ¦ 2022-07-01 00:00:00 select * from t1 where weekday(a) between 0 and 4; -a b -2022-06-01 2022-07-01 00:00:00 +➤ a[91,64,0] ¦ b[93,64,0] 𝄀 +2022-06-01 ¦ 2022-07-01 00:00:00 drop table if exists t1; create table t1(a date, b datetime); insert into t1 values('2022-01-01','2022-01-01 01:01:01'); @@ -608,19 +608,19 @@ insert into t1 values('2022-01-02','2022-01-02 23:01:01'); insert into t1 values('2021-12-31','2021-12-30 23:59:59'); insert into t1 values('2022-06-30','2021-12-30 23:59:59'); select date(a),date(b) from t1; -date(a) date(b) -2022-01-01 2022-01-01 -2022-01-01 2022-01-01 -2022-01-02 2022-01-02 -2021-12-31 2021-12-30 -2022-06-30 2021-12-30 +➤ date(a)[91,64,0] ¦ date(b)[91,64,0] 𝄀 +2022-01-01 ¦ 2022-01-01 𝄀 +2022-01-01 ¦ 2022-01-01 𝄀 +2022-01-02 ¦ 2022-01-02 𝄀 +2021-12-31 ¦ 2021-12-30 𝄀 +2022-06-30 ¦ 2021-12-30 select date(a),date(date(a)) as dda from t1; -date(a) dda -2022-01-01 2022-01-01 -2022-01-01 2022-01-01 -2022-01-02 2022-01-02 -2021-12-31 2021-12-31 -2022-06-30 2022-06-30 +➤ date(a)[91,64,0] ¦ dda[91,64,0] 𝄀 +2022-01-01 ¦ 2022-01-01 𝄀 +2022-01-01 ¦ 2022-01-01 𝄀 +2022-01-02 ¦ 2022-01-02 𝄀 +2021-12-31 ¦ 2021-12-31 𝄀 +2022-06-30 ¦ 2022-06-30 drop table t1; drop table if exists t1; create table t1(a datetime, b timestamp); @@ -632,71 +632,71 @@ insert into t1 values("2022-06-01 14:11:09","2022-07-01 00:00:00"); insert into t1 values("2022-12-31","2011-01-31 12:00:00"); insert into t1 values("2022-06-12","2022-07-01 00:00:00"); select hour(a),hour(b) from t1; -hour(a) hour(b) -0 12 -12 0 -null 23 -0 null -14 0 -0 12 -0 0 +➤ hour(a)[-6,8,0] ¦ hour(b)[-6,8,0] 𝄀 +0 ¦ 12 𝄀 +12 ¦ 0 𝄀 +null ¦ 23 𝄀 +0 ¦ null 𝄀 +14 ¦ 0 𝄀 +0 ¦ 12 𝄀 +0 ¦ 0 select * from t1 where hour(a)>hour(b); -a b -2011-01-31 12:32:11 1979-10-22 00:00:00 -2022-06-01 14:11:09 2022-07-01 00:00:00 +➤ a[93,64,0] ¦ b[93,64,0] 𝄀 +2011-01-31 12:32:11 ¦ 1979-10-22 00:00:00 𝄀 +2022-06-01 14:11:09 ¦ 2022-07-01 00:00:00 select * from t1 where hour(a) between 10 and 16; -a b -2011-01-31 12:32:11 1979-10-22 00:00:00 -2022-06-01 14:11:09 2022-07-01 00:00:00 +➤ a[93,64,0] ¦ b[93,64,0] 𝄀 +2011-01-31 12:32:11 ¦ 1979-10-22 00:00:00 𝄀 +2022-06-01 14:11:09 ¦ 2022-07-01 00:00:00 select minute(a),minute(b) from t1; -minute(a) minute(b) -0 0 -32 0 -null 10 -0 null -11 0 -0 0 -0 0 +➤ minute(a)[-6,8,0] ¦ minute(b)[-6,8,0] 𝄀 +0 ¦ 0 𝄀 +32 ¦ 0 𝄀 +null ¦ 10 𝄀 +0 ¦ null 𝄀 +11 ¦ 0 𝄀 +0 ¦ 0 𝄀 +0 ¦ 0 select * from t1 where minute(a)<=minute(b); -a b -2022-07-01 00:00:00 2011-01-31 12:00:00 -2022-12-31 00:00:00 2011-01-31 12:00:00 -2022-06-12 00:00:00 2022-07-01 00:00:00 +➤ a[93,64,0] ¦ b[93,64,0] 𝄀 +2022-07-01 00:00:00 ¦ 2011-01-31 12:00:00 𝄀 +2022-12-31 00:00:00 ¦ 2011-01-31 12:00:00 𝄀 +2022-06-12 00:00:00 ¦ 2022-07-01 00:00:00 select * from t1 where minute(a) between 10 and 36; -a b -2011-01-31 12:32:11 1979-10-22 00:00:00 -2022-06-01 14:11:09 2022-07-01 00:00:00 +➤ a[93,64,0] ¦ b[93,64,0] 𝄀 +2011-01-31 12:32:11 ¦ 1979-10-22 00:00:00 𝄀 +2022-06-01 14:11:09 ¦ 2022-07-01 00:00:00 select second(a),second(b) from t1; -second(a) second(b) -0 0 -11 0 -null 11 -0 null -9 0 -0 0 -0 0 +➤ second(a)[-6,8,0] ¦ second(b)[-6,8,0] 𝄀 +0 ¦ 0 𝄀 +11 ¦ 0 𝄀 +null ¦ 11 𝄀 +0 ¦ null 𝄀 +9 ¦ 0 𝄀 +0 ¦ 0 𝄀 +0 ¦ 0 select * from t1 where second(a)>=second(b); -a b -2022-07-01 00:00:00 2011-01-31 12:00:00 -2011-01-31 12:32:11 1979-10-22 00:00:00 -2022-06-01 14:11:09 2022-07-01 00:00:00 -2022-12-31 00:00:00 2011-01-31 12:00:00 -2022-06-12 00:00:00 2022-07-01 00:00:00 +➤ a[93,64,0] ¦ b[93,64,0] 𝄀 +2022-07-01 00:00:00 ¦ 2011-01-31 12:00:00 𝄀 +2011-01-31 12:32:11 ¦ 1979-10-22 00:00:00 𝄀 +2022-06-01 14:11:09 ¦ 2022-07-01 00:00:00 𝄀 +2022-12-31 00:00:00 ¦ 2011-01-31 12:00:00 𝄀 +2022-06-12 00:00:00 ¦ 2022-07-01 00:00:00 select * from t1 where second(a) between 10 and 36; -a b -2011-01-31 12:32:11 1979-10-22 00:00:00 +➤ a[93,64,0] ¦ b[93,64,0] 𝄀 +2011-01-31 12:32:11 ¦ 1979-10-22 00:00:00 drop table if exists t1; drop table if exists t1; create table t1(a int, b int); select mo_table_rows(db_name,'t1'),mo_table_size(db_name,'t1') from (select database() as db_name); -mo_table_rows(db_name, t1) mo_table_size(db_name, t1) -0 0 +➤ mo_table_rows(db_name, t1)[-5,64,0] ¦ mo_table_size(db_name, t1)[-5,64,0] 𝄀 +0 ¦ 0 insert into t1 values(1, 2); insert into t1 values(3, 4); set mo_table_stats.use_old_impl = yes; select mo_table_rows(db_name,'t1'),mo_table_size(db_name,'t1') from (select database() as db_name); -mo_table_rows(db_name, t1) mo_table_size(db_name, t1) -2 80 +➤ mo_table_rows(db_name, t1)[-5,64,0] ¦ mo_table_size(db_name, t1)[-5,64,0] 𝄀 +2 ¦ 80 set mo_table_stats.use_old_impl = no; drop table if exists t1; drop database if exists test01; @@ -705,34 +705,42 @@ use test01; create table t(a int, b varchar(10)); insert into t values(1, 'h'), (2, 'b'), (3, 'c'), (4, 'q'), (5, 'd'), (6, 'b'), (7, 's'), (8, 'a'), (9, 'z'), (10, 'm'); select mo_ctl('dn', 'flush', 'test01.t'); -mo_ctl(dn, flush, test01.t) -{\n "method": "Flush",\n "result": [\n {\n "returnStr": "OK"\n }\n ]\n}\n +➤ mo_ctl(dn, flush, test01.t)[12,-1,0] 𝄀 +{ + "method": "Flush", + "result": [ + { + "returnStr": "OK" + } + ] +} + select mo_table_col_max('test01', 't', 'a'), mo_table_col_min('test01', 't', 'a'); -mo_table_col_max(test01, t, a) mo_table_col_min(test01, t, a) -10 1 +➤ mo_table_col_max(test01, t, a)[12,-1,0] ¦ mo_table_col_min(test01, t, a)[12,-1,0] 𝄀 +10 ¦ 1 drop table t; drop database test01; drop database if exists test01; create database test01; use test01; select trim(' abc '), trim('abc '), trim(' abc'), trim('abc'); -trim( abc ) trim(abc ) trim( abc) trim(abc) -abc abc abc abc +➤ trim( abc )[12,-1,0] ¦ trim(abc )[12,-1,0] ¦ trim( abc)[12,-1,0] ¦ trim(abc)[12,-1,0] 𝄀 +abc ¦ abc ¦ abc ¦ abc select trim('abc' from ' abc '), trim('abc' from 'abc '), trim('abc' from ' abc'), trim('abc' from 'abc'); -trim(abc from abc ) trim(abc from abc ) trim(abc from abc) trim(abc from abc) - abc +➤ trim(abc from abc )[12,-1,0] ¦ trim(abc from abc )[12,-1,0] ¦ trim(abc from abc)[12,-1,0] ¦ trim(abc from abc)[12,-1,0] 𝄀 + abc ¦ ¦ ¦ select trim(both from ' abc '), trim(leading from ' abcd'), trim(trailing from ' abc '); -trim(both from abc ) trim(leading from abcd) trim(trailing from abc ) -abc abcd abc +➤ trim(both from abc )[12,-1,0] ¦ trim(leading from abcd)[12,-1,0] ¦ trim(trailing from abc )[12,-1,0] 𝄀 +abc ¦ abcd ¦ abc select trim(both 'abc' from ' abc'), trim(leading 'abc' from 'abcd'), trim(trailing 'abc' from 'axabc'); -trim(both abc from abc) trim(leading abc from abcd) trim(trailing abc from axabc) - d ax +➤ trim(both abc from abc)[12,-1,0] ¦ trim(leading abc from abcd)[12,-1,0] ¦ trim(trailing abc from axabc)[12,-1,0] 𝄀 + ¦ d ¦ ax select trim('嗷嗷' from '嗷嗷abc嗷嗷'), trim(both '嗷嗷' from '嗷嗷abc嗷嗷'), trim(leading '嗷嗷' from '嗷嗷abcd嗷嗷'), trim(trailing '嗷嗷' from '嗷嗷abc嗷嗷'); -trim(嗷嗷 from 嗷嗷abc嗷嗷) trim(both 嗷嗷 from 嗷嗷abc嗷嗷) trim(leading 嗷嗷 from 嗷嗷abcd嗷嗷) trim(trailing 嗷嗷 from 嗷嗷abc嗷嗷) -abc abc abcd嗷嗷 嗷嗷abc +➤ trim(嗷嗷 from 嗷嗷abc嗷嗷)[12,-1,0] ¦ trim(both 嗷嗷 from 嗷嗷abc嗷嗷)[12,-1,0] ¦ trim(leading 嗷嗷 from 嗷嗷abcd嗷嗷)[12,-1,0] ¦ trim(trailing 嗷嗷 from 嗷嗷abc嗷嗷)[12,-1,0] 𝄀 +abc ¦ abc ¦ abcd嗷嗷 ¦ 嗷嗷abc select trim(null from ' abc '), trim('abc' from null), trim(null from null); -trim(null from abc ) trim(abc from null) trim(null from null) -null null null +➤ trim(null from abc )[12,-1,0] ¦ trim(abc from null)[12,-1,0] ¦ trim(null from null)[12,-1,0] 𝄀 +null ¦ null ¦ null drop table if exists t1; create table t1(a varchar(100), b varchar(100)); insert into t1 values('abc', 'abc'); @@ -741,122 +749,124 @@ insert into t1 values('啊啊o', 'o'); insert into t1 values('啊啊o', '啊'); insert into t1 values('啊啊o', 'o啊'); select trim(a from b) from t1; -trim(a from b) - - -o -啊 +➤ trim(a from b)[12,-1,0] 𝄀 + 𝄀 + 𝄀 +o 𝄀 +啊 𝄀 o啊 select trim(both a from b) from t1; -trim(both a from b) - - -o -啊 +➤ trim(both a from b)[12,-1,0] 𝄀 + 𝄀 + 𝄀 +o 𝄀 +啊 𝄀 o啊 select trim(leading a from b) from t1; -trim(leading a from b) - - -o -啊 +➤ trim(leading a from b)[12,-1,0] 𝄀 + 𝄀 + 𝄀 +o 𝄀 +啊 𝄀 o啊 select trim(trailing a from b) from t1; -trim(trailing a from b) - - -o -啊 +➤ trim(trailing a from b)[12,-1,0] 𝄀 + 𝄀 + 𝄀 +o 𝄀 +啊 𝄀 o啊 insert into t1 values(null, 'abc'); select trim(a from b) from t1; -trim(a from b) - - -o -啊 -o啊 +➤ trim(a from b)[12,-1,0] 𝄀 + 𝄀 + 𝄀 +o 𝄀 +啊 𝄀 +o啊 𝄀 null select trim('a' from a) from t1; -trim(a from a) -bc -啊abc哦 -啊啊o -啊啊o -啊啊o +➤ trim(a from a)[12,-1,0] 𝄀 +bc 𝄀 +啊abc哦 𝄀 +啊啊o 𝄀 +啊啊o 𝄀 +啊啊o 𝄀 null select trim(null from b) from t1; -trim(null from b) -null -null -null -null -null +➤ trim(null from b)[12,-1,0] 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 null select trim('a' from null) from t1; -trim(a from null) -null -null -null -null -null +➤ trim(a from null)[12,-1,0] 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 null select trim(null from null) from t1; -trim(null from null) -null -null -null -null -null +➤ trim(null from null)[12,-1,0] 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 +null 𝄀 null drop table t1; create table testt(i int, j int, k int, d1 decimal(10, 2), d2 decimal(10, 2), ts1 timestamp, ts2 timestamp, t varchar(100)); insert into testt select result, result % 7, result + 1, result + 3.14, result + 2.72, current_timestamp, current_timestamp, 'foo' || result from generate_series(1, 100) tmpt; select greatest(1, 2, 3); -greatest(1, 2, 3) +➤ greatest(1, 2, 3)[-5,64,0] 𝄀 3 select least(1, 2, 3); -least(1, 2, 3) +➤ least(1, 2, 3)[-5,64,0] 𝄀 1 select greatest(3, 2, 1); -greatest(3, 2, 1) +➤ greatest(3, 2, 1)[-5,64,0] 𝄀 3 select least(3, 2, 1); -least(3, 2, 1) +➤ least(3, 2, 1)[-5,64,0] 𝄀 1 select greatest(i, j, k), least(i, j, k) from testt order by least(i, j, k) limit 2; -greatest(i, j, k) least(i, j, k) -8 0 -15 0 +➤ greatest(i, j, k)[4,32,0] ¦ least(i, j, k)[4,32,0] 𝄀 +8 ¦ 0 𝄀 +15 ¦ 0 select greatest(i, j, t), least(i, j, t) from testt order by greatest(i, j, k) limit 2; -invalid argument function greatest, bad value [INT INT VARCHAR] +➤ greatest(i, j, t)[12,-1,0] ¦ least(i, j, t)[12,-1,0] 𝄀 +foo1 ¦ 1 𝄀 +foo2 ¦ 2 select max(greatest(d1, d2)), max(least(d1, d2)) from testt; -max(greatest(d1, d2)) max(least(d1, d2)) -103.14 102.72 +➤ max(greatest(d1, d2))[3,10,2] ¦ max(least(d1, d2))[3,10,2] 𝄀 +103.14 ¦ 102.72 select count(*) from testt where greatest(ts1, ts2) = least(ts1, ts3); invalid input: column ts3 does not exist select greatest(null, 1); -greatest(null, 1) +➤ greatest(null, 1)[-5,64,0] 𝄀 null select greatest(1, null); -greatest(1, null) +➤ greatest(1, null)[-5,64,0] 𝄀 null select least(null, 1); -least(null, 1) +➤ least(null, 1)[-5,64,0] 𝄀 null select least(1, null); -least(1, null) +➤ least(1, null)[-5,64,0] 𝄀 null select greatest(null, null); -greatest(null, null) +➤ greatest(null, null)[12,-1,0] 𝄀 null select greatest(i, null) from testt limit 2; -greatest(i, null) -null +➤ greatest(i, null)[4,32,0] 𝄀 +null 𝄀 null select least(null, i) from testt limit 2; -least(null, i) -null +➤ least(null, i)[4,32,0] 𝄀 +null 𝄀 null drop table testt; drop database test01; From 54ef9d9f58f3946c404938c10cea3f78f7443a1c Mon Sep 17 00:00:00 2001 From: daviszhen Date: Tue, 14 Jul 2026 17:07:50 +0800 Subject: [PATCH 08/14] update --- .../func_builtin_leastgreatest_test.go | 136 ++++++++++++++++-- .../function/greatest_least_numeric.result | 3 + .../cases/function/greatest_least_numeric.sql | 1 + 3 files changed, 129 insertions(+), 11 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 9cc209e0fff9c..31b662d5cde28 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -688,6 +688,13 @@ func TestLeastGreatestFunctionResolution(t *testing.T) { wantCast: true, wantTargets: []types.T{types.T_varbinary, types.T_varbinary}, }, + { + name: "text blob returns text", + args: []types.Type{types.T_text.ToType(), types.T_blob.ToType()}, + wantReturn: types.T_text, + wantCast: true, + wantTargets: []types.T{types.T_text, types.T_text}, + }, { name: "enum varchar returns varchar", args: []types.Type{types.T_enum.ToType(), types.T_varchar.ToType()}, @@ -767,6 +774,24 @@ func TestLeastGreatestTemporalResolution(t *testing.T) { wantMode: leastGreatestComparePackedDate, wantTargets: []types.T{types.T_varchar, types.T_date, types.T_time}, }, + { + name: "json datetime keeps temporal input for json temporal overload", + inputs: []types.Type{types.T_json.ToType(), types.T_datetime.ToType()}, + wantOK: true, + wantReturn: types.T_varchar, + wantOverload: leastGreatestJSONTemporalOverload, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_varchar, types.T_datetime}, + }, + { + name: "json timestamp keeps temporal input for json temporal overload", + inputs: []types.Type{types.T_json.ToType(), types.T_timestamp.ToType()}, + wantOK: true, + wantReturn: types.T_varchar, + wantOverload: leastGreatestJSONTemporalOverload, + wantMode: leastGreatestComparePackedDate, + wantTargets: []types.T{types.T_varchar, types.T_timestamp}, + }, { name: "year date bigint keeps temporal peers", inputs: []types.Type{types.T_year.ToType(), types.T_date.ToType(), types.T_int64.ToType()}, @@ -876,6 +901,48 @@ func TestLeastGreatestHighPriorityResolution(t *testing.T) { }) } +func TestLeastGreatestSupportedOidResolution(t *testing.T) { + sameOidCases := []struct { + name string + typ types.Type + }{ + {"uuid", types.T_uuid.ToType()}, + {"rowid", types.T_Rowid.ToType()}, + {"float32 array", types.T_array_float32.ToType()}, + {"float64 array", types.T_array_float64.ToType()}, + {"datalink", types.T_datalink.ToType()}, + } + for _, tc := range sameOidCases { + t.Run(tc.name+" same oid", func(t *testing.T) { + resolution, ok := resolveLeastGreatestType([]types.Type{tc.typ, tc.typ}) + require.True(t, ok) + require.Equal(t, tc.typ.Oid, resolution.resultType.Oid) + require.Equal(t, leastGreatestNormalOverload, resolution.overloadID) + require.Empty(t, resolution.castTypes) + }) + t.Run(tc.name+" mixed oid", func(t *testing.T) { + _, ok := resolveLeastGreatestType([]types.Type{tc.typ, types.T_varchar.ToType()}) + require.False(t, ok) + }) + } + + for _, tc := range []struct { + name string + input []types.Type + }{ + {"interval same oid", []types.Type{{Oid: types.T_interval}, {Oid: types.T_interval}}}, + {"interval mixed oid", []types.Type{{Oid: types.T_interval}, types.T_varchar.ToType()}}, + {"geometry same oid", []types.Type{{Oid: types.T_geometry}, {Oid: types.T_geometry}}}, + {"geometry mixed oid", []types.Type{{Oid: types.T_geometry}, types.T_varchar.ToType()}}, + {"float arrays mixed oid", []types.Type{types.T_array_float32.ToType(), types.T_array_float64.ToType()}}, + } { + t.Run(tc.name, func(t *testing.T) { + _, ok := resolveLeastGreatestType(tc.input) + require.False(t, ok) + }) + } +} + func TestLeastGreatestPackedDateMaterializesNullRows(t *testing.T) { proc := testutil.NewProcess(t) d1, err := types.ParseDateCast("2020-01-01") @@ -992,21 +1059,68 @@ func TestLeastGreatestYearNumericExecutor(t *testing.T) { leastYearNumericFn) ok, info = tcLeast.Run() require.True(t, ok, info) + + t.Run("double target", func(t *testing.T) { + inputs := []FunctionTestInput{ + NewFunctionTestInput(types.T_year.ToType(), []types.MoYear{2020, 2022}, nil), + NewFunctionTestInput(types.T_float64.ToType(), []float64{2019.5, 2022.5}, nil), + } + greatest := NewFunctionTestCase(proc, inputs, + NewFunctionTestResult(types.T_float64.ToType(), false, []float64{2020, 2022.5}, nil), + greatestYearNumericFn) + ok, info := greatest.Run() + require.True(t, ok, info) + + least := NewFunctionTestCase(proc, inputs, + NewFunctionTestResult(types.T_float64.ToType(), false, []float64{2019.5, 2022}, nil), + leastYearNumericFn) + ok, info = least.Run() + require.True(t, ok, info) + }) + + t.Run("bit target", func(t *testing.T) { + inputs := []FunctionTestInput{ + NewFunctionTestInput(types.T_year.ToType(), []types.MoYear{2020, 2022}, nil), + NewFunctionTestInput(types.T_bit.ToType(), []uint64{2019, 2023}, nil), + } + greatest := NewFunctionTestCase(proc, inputs, + NewFunctionTestResult(types.T_uint64.ToType(), false, []uint64{2020, 2023}, nil), + greatestYearNumericFn) + ok, info := greatest.Run() + require.True(t, ok, info) + + least := NewFunctionTestCase(proc, inputs, + NewFunctionTestResult(types.T_uint64.ToType(), false, []uint64{2019, 2022}, nil), + leastYearNumericFn) + ok, info = least.Run() + require.True(t, ok, info) + }) } func TestLeastGreatestYearNumericFunctionResolution(t *testing.T) { - for _, name := range []string{"greatest", "least"} { - fn, err := GetFunctionByName(context.Background(), name, []types.Type{ - types.T_year.ToType(), - types.New(types.T_decimal128, 10, 1), - }) - require.NoError(t, err) - require.Equal(t, int32(leastGreatestYearNumericOverload), fn.overloadId) - require.Equal(t, types.T_decimal128, fn.GetReturnType().Oid) + cases := []struct { + name string + numericTyp types.Type + wantTarget types.T + }{ + {"bigint", types.T_int64.ToType(), types.T_decimal128}, + {"decimal", types.New(types.T_decimal128, 10, 1), types.T_decimal128}, + {"double", types.T_float64.ToType(), types.T_float64}, + {"bit", types.T_bit.ToType(), types.T_uint64}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + for _, name := range []string{"greatest", "least"} { + fn, err := GetFunctionByName(context.Background(), name, []types.Type{types.T_year.ToType(), tc.numericTyp}) + require.NoError(t, err) + require.Equal(t, int32(leastGreatestYearNumericOverload), fn.overloadId) + require.Equal(t, tc.wantTarget, fn.GetReturnType().Oid) - targets, shouldCast := fn.ShouldDoImplicitTypeCast() - require.True(t, shouldCast) - require.Equal(t, []types.T{types.T_year, types.T_decimal128}, []types.T{targets[0].Oid, targets[1].Oid}) + targets, shouldCast := fn.ShouldDoImplicitTypeCast() + require.True(t, shouldCast) + require.Equal(t, []types.T{types.T_year, tc.wantTarget}, []types.T{targets[0].Oid, targets[1].Oid}) + } + }) } } diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index c06b4a57ea146..dacef8e5acc5f 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -348,6 +348,9 @@ invalid argument function greatest, bad value [ROWID BIGINT] select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; ➤ greatest_vecf32_same_oid[12,3,0] 𝄀 [1, 2, 3] +select greatest(v64, v64) as greatest_vecf64_same_oid from greatest_least_supported_oid; +➤ greatest_vecf64_same_oid[12,3,0] 𝄀 +[1, 2, 3] select greatest(v32, v64) from greatest_least_supported_oid; invalid argument function greatest, bad value [VECF32 VECF64] select greatest(cast('file://greatest-least-a' as datalink), cast('file://greatest-least-b' as datalink)) as greatest_datalink_same_oid; diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index dff4092192d9a..7440160b74670 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -193,6 +193,7 @@ select greatest(cast('00000000-0000-0000-0000-000000000001' as uuid), 'x'); select greatest(__mo_rowid, __mo_rowid) = __mo_rowid as greatest_rowid_same_oid from greatest_least_supported_oid; select greatest(__mo_rowid, 1) from greatest_least_supported_oid; select greatest(v32, v32) as greatest_vecf32_same_oid from greatest_least_supported_oid; +select greatest(v64, v64) as greatest_vecf64_same_oid from greatest_least_supported_oid; select greatest(v32, v64) from greatest_least_supported_oid; select greatest(cast('file://greatest-least-a' as datalink), cast('file://greatest-least-b' as datalink)) as greatest_datalink_same_oid; select greatest(cast('file://greatest-least-a' as datalink), 'x'); From 548ead927961f68cfb80d7e6d99b24780cd596e4 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Wed, 15 Jul 2026 11:40:58 +0800 Subject: [PATCH 09/14] update --- .../plan/base_binder_leastgreatest_test.go | 14 ++ .../function/func_builtin_leastgreatest.go | 21 ++- .../func_builtin_leastgreatest_test.go | 142 ++++++++++++++++++ .../function/greatest_least_numeric.result | 18 +++ .../cases/function/greatest_least_numeric.sql | 9 ++ 5 files changed, 203 insertions(+), 1 deletion(-) diff --git a/pkg/sql/plan/base_binder_leastgreatest_test.go b/pkg/sql/plan/base_binder_leastgreatest_test.go index 84ecacc28163e..e5387b01e932a 100644 --- a/pkg/sql/plan/base_binder_leastgreatest_test.go +++ b/pkg/sql/plan/base_binder_leastgreatest_test.go @@ -41,6 +41,20 @@ func TestBindLeastGreatestTemporalScale(t *testing.T) { } }) } + + t.Run("mixed temporal oids preserve max scale", func(t *testing.T) { + args := []*planpb.Expr{ + {Typ: planpb.Type{Id: int32(types.T_date), Width: 64}}, + {Typ: planpb.Type{Id: int32(types.T_datetime), Width: 64, Scale: 1}}, + {Typ: planpb.Type{Id: int32(types.T_datetime), Width: 64, Scale: 6}}, + } + for _, name := range []string{"greatest", "least"} { + expr, err := BindFuncExprImplByPlanExpr(context.Background(), name, args) + require.NoError(t, err, name) + require.Equal(t, int32(types.T_datetime), expr.Typ.Id, name) + require.Equal(t, int32(6), expr.Typ.Scale, name) + } + }) } func TestBuildLeastGreatestTemporalScale(t *testing.T) { diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index fbf23f67e4598..9a3f9cca7e76e 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -376,9 +376,26 @@ func leastGreatestTemporalItemType(inputs []types.Type) types.Type { best = inputs[i] } } + if leastGreatestTemporalSupportsScale(best.Oid) { + best.Scale = leastGreatestTemporalMaxScale(inputs) + } return best } +func leastGreatestTemporalMaxScale(inputs []types.Type) int32 { + var maxScale int32 + for i := range inputs { + if leastGreatestTemporalSupportsScale(inputs[i].Oid) && inputs[i].Scale > maxScale { + maxScale = inputs[i].Scale + } + } + return maxScale +} + +func leastGreatestTemporalSupportsScale(oid types.T) bool { + return oid == types.T_time || oid == types.T_datetime || oid == types.T_timestamp +} + func leastGreatestTemporalRank(oid types.T) int { switch oid { case types.T_datetime: @@ -438,7 +455,9 @@ func leastGreatestDateTemporalMixedType(inputs []types.Type) (types.Type, bool) } switch { case allTemporal: - return types.T_datetime.ToType(), true + target := types.T_datetime.ToType() + target.Scale = leastGreatestTemporalMaxScale(inputs) + return target, true case hasText: return types.T_text.ToType(), true case hasNonBinary || hasNumeric: diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 31b662d5cde28..c067bba322ccc 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -825,6 +825,45 @@ func TestLeastGreatestTemporalResolution(t *testing.T) { } } +func TestLeastGreatestMixedTemporalResolutionPreservesMaxScale(t *testing.T) { + datetimeScale1 := types.New(types.T_datetime, 64, 1) + datetimeScale2 := types.New(types.T_datetime, 64, 2) + datetimeScale6 := types.New(types.T_datetime, 64, 6) + timestampScale6 := types.New(types.T_timestamp, 64, 6) + + for _, tc := range []struct { + inputs []types.Type + wantScale int32 + }{ + {[]types.Type{datetimeScale1, datetimeScale2, types.T_varchar.ToType()}, 2}, + {[]types.Type{types.T_varchar.ToType(), datetimeScale2, datetimeScale1}, 2}, + {[]types.Type{timestampScale6, datetimeScale1, types.T_varchar.ToType()}, 6}, + } { + resolution, ok := resolveLeastGreatestType(tc.inputs) + require.True(t, ok) + require.Equal(t, types.T_varchar, resolution.resultType.Oid) + require.Equal(t, types.T_datetime, resolution.temporalItemType.Oid) + require.Equal(t, tc.wantScale, resolution.temporalItemType.Scale) + } + + resolution, ok := resolveLeastGreatestType([]types.Type{ + types.T_date.ToType(), datetimeScale1, datetimeScale6, + }) + require.True(t, ok) + require.Equal(t, types.T_datetime, resolution.resultType.Oid) + require.Equal(t, int32(6), resolution.resultType.Scale) + require.Equal(t, types.T_datetime, resolution.temporalItemType.Oid) + require.Equal(t, int32(6), resolution.temporalItemType.Scale) + + resolution, ok = resolveLeastGreatestType([]types.Type{ + types.T_json.ToType(), datetimeScale1, datetimeScale6, + }) + require.True(t, ok) + require.Equal(t, types.T_varchar, resolution.resultType.Oid) + require.Equal(t, types.T_datetime, resolution.temporalItemType.Oid) + require.Equal(t, int32(6), resolution.temporalItemType.Scale) +} + func TestLeastGreatestHighPriorityResolution(t *testing.T) { decimalScale1 := types.New(types.T_decimal64, 10, 1) decimalScale2 := types.New(types.T_decimal64, 12, 2) @@ -1197,6 +1236,109 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { require.True(t, ok, info) } +func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { + proc := testutil.NewProcess(t) + datetimeScale1 := types.New(types.T_datetime, 64, 1) + datetimeScale2 := types.New(types.T_datetime, 64, 2) + timestampScale6 := types.New(types.T_timestamp, 64, 6) + varcharType := types.T_varchar.ToType() + + parseDatetime := func(value string, scale int32) types.Datetime { + datetime, err := types.ParseDatetime(value, scale) + require.NoError(t, err) + return datetime + } + parseTimestamp := func(value string, scale int32) types.Timestamp { + timestamp, err := types.ParseTimestamp(time.Local, value, scale) + require.NoError(t, err) + return timestamp + } + + type testCase struct { + name string + fn fEvalFn + first FunctionTestInput + second FunctionTestInput + text string + expected string + } + + for _, tc := range []testCase{ + { + name: "greatest keeps varchar peer precision", + fn: greatestTemporalFn, + first: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.1", 1)}, nil), + second: NewFunctionTestInput(datetimeScale2, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.24", 2)}, nil), + text: "2020-01-01 00:00:00.25", + expected: "2020-01-01 00:00:00.25", + }, + { + name: "greatest is permutation invariant", + fn: greatestTemporalFn, + first: NewFunctionTestInput(datetimeScale2, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.24", 2)}, nil), + second: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.1", 1)}, nil), + text: "2020-01-01 00:00:00.25", + expected: "2020-01-01 00:00:00.25", + }, + { + name: "greatest json temporal overload keeps peer precision", + fn: greatestJSONTemporalFn, + first: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.1", 1)}, nil), + second: NewFunctionTestInput(datetimeScale2, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.24", 2)}, nil), + text: "2020-01-01 00:00:00.25", + expected: "2020-01-01 00:00:00.25", + }, + { + name: "greatest mixed datetime timestamp uses max scale", + fn: greatestTemporalFn, + first: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.1", 1)}, nil), + second: NewFunctionTestInput(timestampScale6, + []types.Timestamp{parseTimestamp("2020-01-01 00:00:00.123456", 6)}, nil), + text: "2020-01-01 00:00:00.123457", + expected: "2020-01-01 00:00:00.123457", + }, + { + name: "least keeps varchar peer precision", + fn: leastTemporalFn, + first: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.3", 1)}, nil), + second: NewFunctionTestInput(datetimeScale2, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.26", 2)}, nil), + text: "2020-01-01 00:00:00.25", + expected: "2020-01-01 00:00:00.25", + }, + { + name: "least is permutation invariant", + fn: leastTemporalFn, + first: NewFunctionTestInput(datetimeScale2, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.26", 2)}, nil), + second: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.3", 1)}, nil), + text: "2020-01-01 00:00:00.25", + expected: "2020-01-01 00:00:00.25", + }, + } { + t.Run(tc.name, func(t *testing.T) { + ftc := NewFunctionTestCase(proc, + []FunctionTestInput{ + tc.first, + tc.second, + NewFunctionTestInput(varcharType, []string{tc.text}, nil), + }, + NewFunctionTestResult(varcharType, false, []string{tc.expected}, nil), tc.fn) + ok, info := ftc.Run() + require.True(t, ok, info) + }) + } +} + func TestLeastGreatestPackedDateRejectsTimeFallback(t *testing.T) { _, err := leastGreatestParsePackedDateBytes([]byte("1"), types.T_date.ToType(), nil, time.Local) require.Error(t, err) diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index dacef8e5acc5f..53683c2f4e3d6 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -221,6 +221,24 @@ select greatest(cast('10:00:00' as time), cast(2 as bit(4))) as greatest_time_bi select greatest(cast('10:00:00.1' as time(1)), cast('10:00:00.99' as time(2))) as greatest_time_scale; ➤ greatest_time_scale[92,64,0] 𝄀 10:00:00.990000000 +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), '2020-01-01 00:00:00.25') as greatest_mixed_datetime_scale; +➤ greatest_mixed_datetime_scale[12,-1,0] 𝄀 +2020-01-01 00:00:00.25 +select greatest(cast('2020-01-01 00:00:00.24' as datetime(2)), cast('2020-01-01 00:00:00.1' as datetime(1)), '2020-01-01 00:00:00.25') as greatest_mixed_datetime_scale_reordered; +➤ greatest_mixed_datetime_scale_reordered[12,-1,0] 𝄀 +2020-01-01 00:00:00.25 +select least(cast('2020-01-01 00:00:00.3' as datetime(1)), cast('2020-01-01 00:00:00.26' as datetime(2)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale; +➤ least_mixed_datetime_scale[12,-1,0] 𝄀 +2020-01-01 00:00:00.25 +select least(cast('2020-01-01 00:00:00.26' as datetime(2)), cast('2020-01-01 00:00:00.3' as datetime(1)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale_reordered; +➤ least_mixed_datetime_scale_reordered[12,-1,0] 𝄀 +2020-01-01 00:00:00.25 +select greatest(cast('2020-01-01' as date), cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as datetime(6))) as greatest_all_temporal_max_fsp; +➤ greatest_all_temporal_max_fsp[93,64,0] 𝄀 +2020-01-01 00:00:00.123456000 +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as greatest_json_mixed_datetime_scale; +➤ greatest_json_mixed_datetime_scale[12,-1,0] 𝄀 +2020-01-01 00:00:00.25 select greatest(null, null) as greatest_all_null, least(null, null) as least_all_null; ➤ greatest_all_null[12,-1,0] ¦ least_all_null[12,-1,0] 𝄀 null ¦ null diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 7440160b74670..6a8082a188f6f 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -129,6 +129,15 @@ select greatest(cast('10:00:00' as time), cast(2 as bit(4))) as greatest_time_bi -- Same-Oid temporal metadata aligns scale before comparison. select greatest(cast('10:00:00.1' as time(1)), cast('10:00:00.99' as time(2))) as greatest_time_scale; +-- Mixed temporal comparisons derive a permutation-invariant common FSP from +-- all temporal inputs before parsing string or JSON peers. +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), '2020-01-01 00:00:00.25') as greatest_mixed_datetime_scale; +select greatest(cast('2020-01-01 00:00:00.24' as datetime(2)), cast('2020-01-01 00:00:00.1' as datetime(1)), '2020-01-01 00:00:00.25') as greatest_mixed_datetime_scale_reordered; +select least(cast('2020-01-01 00:00:00.3' as datetime(1)), cast('2020-01-01 00:00:00.26' as datetime(2)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale; +select least(cast('2020-01-01 00:00:00.26' as datetime(2)), cast('2020-01-01 00:00:00.3' as datetime(1)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale_reordered; +select greatest(cast('2020-01-01' as date), cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as datetime(6))) as greatest_all_temporal_max_fsp; +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as greatest_json_mixed_datetime_scale; + -- All T_any arguments return a VARCHAR NULL result, and any NULL argument -- makes the strict function result NULL. select greatest(null, null) as greatest_all_null, least(null, null) as least_all_null; From 76e7a89670c05efaa5d1c5b024a456a91a70f05a Mon Sep 17 00:00:00 2001 From: daviszhen Date: Wed, 15 Jul 2026 12:07:23 +0800 Subject: [PATCH 10/14] update --- .../function/func_builtin_leastgreatest.go | 12 ++++ .../func_builtin_leastgreatest_test.go | 57 +++++++++++++++++++ .../function/greatest_least_numeric.result | 9 +++ .../cases/function/greatest_least_numeric.sql | 6 ++ 4 files changed, 84 insertions(+) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index 9a3f9cca7e76e..a2fe1debeea81 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -376,12 +376,24 @@ func leastGreatestTemporalItemType(inputs []types.Type) types.Type { best = inputs[i] } } + if best.Oid == types.T_date && leastGreatestHasTimeBearingTemporal(inputs) { + best = types.T_datetime.ToType() + } if leastGreatestTemporalSupportsScale(best.Oid) { best.Scale = leastGreatestTemporalMaxScale(inputs) } return best } +func leastGreatestHasTimeBearingTemporal(inputs []types.Type) bool { + for i := range inputs { + if leastGreatestTemporalSupportsScale(inputs[i].Oid) { + return true + } + } + return false +} + func leastGreatestTemporalMaxScale(inputs []types.Type) int32 { var maxScale int32 for i := range inputs { diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index c067bba322ccc..7dcda80d786f6 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -830,6 +830,7 @@ func TestLeastGreatestMixedTemporalResolutionPreservesMaxScale(t *testing.T) { datetimeScale2 := types.New(types.T_datetime, 64, 2) datetimeScale6 := types.New(types.T_datetime, 64, 6) timestampScale6 := types.New(types.T_timestamp, 64, 6) + timeScale6 := types.New(types.T_time, 64, 6) for _, tc := range []struct { inputs []types.Type @@ -862,6 +863,16 @@ func TestLeastGreatestMixedTemporalResolutionPreservesMaxScale(t *testing.T) { require.Equal(t, types.T_varchar, resolution.resultType.Oid) require.Equal(t, types.T_datetime, resolution.temporalItemType.Oid) require.Equal(t, int32(6), resolution.temporalItemType.Scale) + + for _, inputs := range [][]types.Type{ + {types.T_date.ToType(), timeScale6, types.T_varchar.ToType()}, + {types.T_json.ToType(), types.T_date.ToType(), timeScale6}, + } { + resolution, ok := resolveLeastGreatestType(inputs) + require.True(t, ok) + require.Equal(t, types.T_datetime, resolution.temporalItemType.Oid) + require.Equal(t, int32(6), resolution.temporalItemType.Scale) + } } func TestLeastGreatestHighPriorityResolution(t *testing.T) { @@ -1238,9 +1249,15 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { proc := testutil.NewProcess(t) + loc := time.FixedZone("UTC", 0) + proc.GetSessionInfo().TimeZone = loc + stmtProfile := &process.StmtProfile{} + stmtProfile.SetQueryStart(time.Date(2024, 5, 6, 1, 2, 3, 0, loc)) + proc.SetStmtProfile(stmtProfile) datetimeScale1 := types.New(types.T_datetime, 64, 1) datetimeScale2 := types.New(types.T_datetime, 64, 2) timestampScale6 := types.New(types.T_timestamp, 64, 6) + timeScale6 := types.New(types.T_time, 64, 6) varcharType := types.T_varchar.ToType() parseDatetime := func(value string, scale int32) types.Datetime { @@ -1253,6 +1270,16 @@ func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { require.NoError(t, err) return timestamp } + parseDate := func(value string) types.Date { + date, err := types.ParseDateCast(value) + require.NoError(t, err) + return date + } + parseTime := func(value string, scale int32) types.Time { + timeValue, err := types.ParseTime(value, scale) + require.NoError(t, err) + return timeValue + } type testCase struct { name string @@ -1304,6 +1331,36 @@ func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { text: "2020-01-01 00:00:00.123457", expected: "2020-01-01 00:00:00.123457", }, + { + name: "greatest date time varchar preserves time peer", + fn: greatestTemporalFn, + first: NewFunctionTestInput(types.T_date.ToType(), + []types.Date{parseDate("2024-05-05")}, nil), + second: NewFunctionTestInput(timeScale6, + []types.Time{parseTime("12:00:00.500000", 6)}, nil), + text: "2024-05-06 13:00:00.250000", + expected: "2024-05-06 13:00:00.250000", + }, + { + name: "least date time varchar preserves time peer", + fn: leastTemporalFn, + first: NewFunctionTestInput(types.T_date.ToType(), + []types.Date{parseDate("2024-05-07")}, nil), + second: NewFunctionTestInput(timeScale6, + []types.Time{parseTime("12:00:00.500000", 6)}, nil), + text: "2024-05-06 11:00:00.250000", + expected: "2024-05-06 11:00:00.250000", + }, + { + name: "greatest json date time preserves time peer", + fn: greatestJSONTemporalFn, + first: NewFunctionTestInput(types.T_date.ToType(), + []types.Date{parseDate("2024-05-05")}, nil), + second: NewFunctionTestInput(timeScale6, + []types.Time{parseTime("12:00:00.500000", 6)}, nil), + text: "2024-05-06 13:00:00.250000", + expected: "2024-05-06 13:00:00.250000", + }, { name: "least keeps varchar peer precision", fn: leastTemporalFn, diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 53683c2f4e3d6..cca1910b11aee 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -239,6 +239,15 @@ select greatest(cast('2020-01-01' as date), cast('2020-01-01 00:00:00.1' as date select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as greatest_json_mixed_datetime_scale; ➤ greatest_json_mixed_datetime_scale[12,-1,0] 𝄀 2020-01-01 00:00:00.25 +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00.500000' as time(6)), concat(cast(current_date() as char), ' 13:00:00.250000')) as datetime) = cast(concat(cast(current_date() as char), ' 13:00:00.250000') as datetime) as greatest_date_time_varchar_preserves_time; +➤ greatest_date_time_varchar_preserves_time[-7,1,0] 𝄀 +1 +select cast(least(cast('2999-01-01' as date), cast('12:00:00.500000' as time(6)), concat(cast(current_date() as char), ' 11:00:00.250000')) as datetime) = cast(concat(cast(current_date() as char), ' 11:00:00.250000') as datetime) as least_date_time_varchar_preserves_time; +➤ least_date_time_varchar_preserves_time[-7,1,0] 𝄀 +1 +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00.500000' as time(6)), json_extract(concat('"', cast(current_date() as char), ' 13:00:00.250000"'), '$')) as datetime) = cast(concat(cast(current_date() as char), ' 13:00:00.250000') as datetime) as greatest_json_date_time_preserves_time; +➤ greatest_json_date_time_preserves_time[-7,1,0] 𝄀 +1 select greatest(null, null) as greatest_all_null, least(null, null) as least_all_null; ➤ greatest_all_null[12,-1,0] ¦ least_all_null[12,-1,0] 𝄀 null ¦ null diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 6a8082a188f6f..6b0a41d80514c 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -138,6 +138,12 @@ select least(cast('2020-01-01 00:00:00.26' as datetime(2)), cast('2020-01-01 00: select greatest(cast('2020-01-01' as date), cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as datetime(6))) as greatest_all_temporal_max_fsp; select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as greatest_json_mixed_datetime_scale; +-- DATE + TIME needs a DATETIME peer target so VARCHAR/JSON time components +-- survive packed-date comparison and formatting. TIME uses the statement date. +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00.500000' as time(6)), concat(cast(current_date() as char), ' 13:00:00.250000')) as datetime) = cast(concat(cast(current_date() as char), ' 13:00:00.250000') as datetime) as greatest_date_time_varchar_preserves_time; +select cast(least(cast('2999-01-01' as date), cast('12:00:00.500000' as time(6)), concat(cast(current_date() as char), ' 11:00:00.250000')) as datetime) = cast(concat(cast(current_date() as char), ' 11:00:00.250000') as datetime) as least_date_time_varchar_preserves_time; +select cast(greatest(cast('2020-01-01' as date), cast('12:00:00.500000' as time(6)), json_extract(concat('"', cast(current_date() as char), ' 13:00:00.250000"'), '$')) as datetime) = cast(concat(cast(current_date() as char), ' 13:00:00.250000') as datetime) as greatest_json_date_time_preserves_time; + -- All T_any arguments return a VARCHAR NULL result, and any NULL argument -- makes the strict function result NULL. select greatest(null, null) as greatest_all_null, least(null, null) as least_all_null; From d644b17d8a25794d3dbb9746ae62eacf94fa0403 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Wed, 15 Jul 2026 12:29:00 +0800 Subject: [PATCH 11/14] update --- .../func_builtin_leastgreatest_test.go | 42 ++++++++++++++++++- .../function/greatest_least_numeric.result | 15 +++++++ .../cases/function/greatest_least_numeric.sql | 5 +++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 7dcda80d786f6..586976d97a03b 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -1266,7 +1266,7 @@ func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { return datetime } parseTimestamp := func(value string, scale int32) types.Timestamp { - timestamp, err := types.ParseTimestamp(time.Local, value, scale) + timestamp, err := types.ParseTimestamp(loc, value, scale) require.NoError(t, err) return timestamp } @@ -1331,6 +1331,16 @@ func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { text: "2020-01-01 00:00:00.123457", expected: "2020-01-01 00:00:00.123457", }, + { + name: "greatest mixed datetime timestamp is permutation invariant", + fn: greatestTemporalFn, + first: NewFunctionTestInput(timestampScale6, + []types.Timestamp{parseTimestamp("2020-01-01 00:00:00.123456", 6)}, nil), + second: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.1", 1)}, nil), + text: "2020-01-01 00:00:00.123457", + expected: "2020-01-01 00:00:00.123457", + }, { name: "greatest date time varchar preserves time peer", fn: greatestTemporalFn, @@ -1361,6 +1371,36 @@ func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { text: "2024-05-06 13:00:00.250000", expected: "2024-05-06 13:00:00.250000", }, + { + name: "least json temporal overload keeps peer precision", + fn: leastJSONTemporalFn, + first: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.3", 1)}, nil), + second: NewFunctionTestInput(datetimeScale2, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.26", 2)}, nil), + text: "2020-01-01 00:00:00.25", + expected: "2020-01-01 00:00:00.25", + }, + { + name: "least mixed datetime timestamp uses max scale", + fn: leastTemporalFn, + first: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.2", 1)}, nil), + second: NewFunctionTestInput(timestampScale6, + []types.Timestamp{parseTimestamp("2020-01-01 00:00:00.123456", 6)}, nil), + text: "2020-01-01 00:00:00.123455", + expected: "2020-01-01 00:00:00.123455", + }, + { + name: "least mixed datetime timestamp is permutation invariant", + fn: leastTemporalFn, + first: NewFunctionTestInput(timestampScale6, + []types.Timestamp{parseTimestamp("2020-01-01 00:00:00.123456", 6)}, nil), + second: NewFunctionTestInput(datetimeScale1, + []types.Datetime{parseDatetime("2020-01-01 00:00:00.2", 1)}, nil), + text: "2020-01-01 00:00:00.123455", + expected: "2020-01-01 00:00:00.123455", + }, { name: "least keeps varchar peer precision", fn: leastTemporalFn, diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index cca1910b11aee..22d6083b0f2ea 100644 --- a/test/distributed/cases/function/greatest_least_numeric.result +++ b/test/distributed/cases/function/greatest_least_numeric.result @@ -233,12 +233,27 @@ select least(cast('2020-01-01 00:00:00.3' as datetime(1)), cast('2020-01-01 00:0 select least(cast('2020-01-01 00:00:00.26' as datetime(2)), cast('2020-01-01 00:00:00.3' as datetime(1)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale_reordered; ➤ least_mixed_datetime_scale_reordered[12,-1,0] 𝄀 2020-01-01 00:00:00.25 +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as timestamp(6)), '2020-01-01 00:00:00.123457') as greatest_datetime_timestamp_scale; +➤ greatest_datetime_timestamp_scale[12,-1,0] 𝄀 +2020-01-01 00:00:00.123457 +select greatest(cast('2020-01-01 00:00:00.123456' as timestamp(6)), cast('2020-01-01 00:00:00.1' as datetime(1)), '2020-01-01 00:00:00.123457') as greatest_datetime_timestamp_scale_reordered; +➤ greatest_datetime_timestamp_scale_reordered[12,-1,0] 𝄀 +2020-01-01 00:00:00.123457 +select least(cast('2020-01-01 00:00:00.2' as datetime(1)), cast('2020-01-01 00:00:00.123456' as timestamp(6)), '2020-01-01 00:00:00.123455') as least_datetime_timestamp_scale; +➤ least_datetime_timestamp_scale[12,-1,0] 𝄀 +2020-01-01 00:00:00.123455 +select least(cast('2020-01-01 00:00:00.123456' as timestamp(6)), cast('2020-01-01 00:00:00.2' as datetime(1)), '2020-01-01 00:00:00.123455') as least_datetime_timestamp_scale_reordered; +➤ least_datetime_timestamp_scale_reordered[12,-1,0] 𝄀 +2020-01-01 00:00:00.123455 select greatest(cast('2020-01-01' as date), cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as datetime(6))) as greatest_all_temporal_max_fsp; ➤ greatest_all_temporal_max_fsp[93,64,0] 𝄀 2020-01-01 00:00:00.123456000 select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as greatest_json_mixed_datetime_scale; ➤ greatest_json_mixed_datetime_scale[12,-1,0] 𝄀 2020-01-01 00:00:00.25 +select least(cast('2020-01-01 00:00:00.3' as datetime(1)), cast('2020-01-01 00:00:00.26' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as least_json_mixed_datetime_scale; +➤ least_json_mixed_datetime_scale[12,-1,0] 𝄀 +2020-01-01 00:00:00.25 select cast(greatest(cast('2020-01-01' as date), cast('12:00:00.500000' as time(6)), concat(cast(current_date() as char), ' 13:00:00.250000')) as datetime) = cast(concat(cast(current_date() as char), ' 13:00:00.250000') as datetime) as greatest_date_time_varchar_preserves_time; ➤ greatest_date_time_varchar_preserves_time[-7,1,0] 𝄀 1 diff --git a/test/distributed/cases/function/greatest_least_numeric.sql b/test/distributed/cases/function/greatest_least_numeric.sql index 6b0a41d80514c..dff9e141ccd1b 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -135,8 +135,13 @@ select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 0 select greatest(cast('2020-01-01 00:00:00.24' as datetime(2)), cast('2020-01-01 00:00:00.1' as datetime(1)), '2020-01-01 00:00:00.25') as greatest_mixed_datetime_scale_reordered; select least(cast('2020-01-01 00:00:00.3' as datetime(1)), cast('2020-01-01 00:00:00.26' as datetime(2)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale; select least(cast('2020-01-01 00:00:00.26' as datetime(2)), cast('2020-01-01 00:00:00.3' as datetime(1)), '2020-01-01 00:00:00.25') as least_mixed_datetime_scale_reordered; +select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as timestamp(6)), '2020-01-01 00:00:00.123457') as greatest_datetime_timestamp_scale; +select greatest(cast('2020-01-01 00:00:00.123456' as timestamp(6)), cast('2020-01-01 00:00:00.1' as datetime(1)), '2020-01-01 00:00:00.123457') as greatest_datetime_timestamp_scale_reordered; +select least(cast('2020-01-01 00:00:00.2' as datetime(1)), cast('2020-01-01 00:00:00.123456' as timestamp(6)), '2020-01-01 00:00:00.123455') as least_datetime_timestamp_scale; +select least(cast('2020-01-01 00:00:00.123456' as timestamp(6)), cast('2020-01-01 00:00:00.2' as datetime(1)), '2020-01-01 00:00:00.123455') as least_datetime_timestamp_scale_reordered; select greatest(cast('2020-01-01' as date), cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.123456' as datetime(6))) as greatest_all_temporal_max_fsp; select greatest(cast('2020-01-01 00:00:00.1' as datetime(1)), cast('2020-01-01 00:00:00.24' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as greatest_json_mixed_datetime_scale; +select least(cast('2020-01-01 00:00:00.3' as datetime(1)), cast('2020-01-01 00:00:00.26' as datetime(2)), json_extract('"2020-01-01 00:00:00.25"', '$')) as least_json_mixed_datetime_scale; -- DATE + TIME needs a DATETIME peer target so VARCHAR/JSON time components -- survive packed-date comparison and formatting. TIME uses the statement date. From 3d1a60e6f72573a199e4069ceb5a3aeb9a39e161 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Fri, 17 Jul 2026 12:42:25 +0800 Subject: [PATCH 12/14] update --- .../function/func_builtin_leastgreatest.go | 74 ++++++++++++++----- .../func_builtin_leastgreatest_test.go | 61 +++++++++++++++ 2 files changed, 118 insertions(+), 17 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index a2fe1debeea81..1cf3cdb11446b 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -996,6 +996,15 @@ func leastGreatestFnPackedDateResolved( } } + readers := make([]leastGreatestTemporalReader, len(parameters)) + for i, pv := range parameters { + reader, err := newLeastGreatestTemporalReader(pv) + if err != nil { + return err + } + readers[i] = reader + } + loc := time.Local if proc != nil && proc.GetSessionInfo() != nil && proc.GetSessionInfo().TimeZone != nil { loc = proc.GetSessionInfo().TimeZone @@ -1016,14 +1025,14 @@ func leastGreatestFnPackedDateResolved( } var winner types.Datetime - for j, pv := range parameters { - if pv.IsNull(i) { + for j := range readers { + if readers[j].source.IsNull(i) { if err := leastGreatestAppendPackedDateResult(result, resolution, types.Datetime(0), loc, true); err != nil { return err } goto nextRow } - v, err := leastGreatestDatetimeValue(pv, i, resolution.temporalItemType, proc, loc) + v, err := readers[j].datetimeValue(i, resolution.temporalItemType, proc, loc) if err != nil { return err } @@ -1057,32 +1066,63 @@ func leastGreatestSetNullResult(result vector.FunctionResultWrapper, length int) return nil } -func leastGreatestDatetimeValue(v *vector.Vector, row uint64, temporalItemType types.Type, proc *process.Process, loc *time.Location) (types.Datetime, error) { +type leastGreatestTemporalReader struct { + source *vector.Vector + date vector.FunctionParameterWrapper[types.Date] + datetime vector.FunctionParameterWrapper[types.Datetime] + timestamp vector.FunctionParameterWrapper[types.Timestamp] + time vector.FunctionParameterWrapper[types.Time] + year vector.FunctionParameterWrapper[types.MoYear] +} + +func newLeastGreatestTemporalReader(v *vector.Vector) (leastGreatestTemporalReader, error) { + reader := leastGreatestTemporalReader{source: v} switch v.GetType().Oid { case types.T_date: - p := vector.GenerateFunctionFixedTypeParameter[types.Date](v) - val, _ := p.GetValue(row) + reader.date = vector.GenerateFunctionFixedTypeParameter[types.Date](v) + case types.T_datetime: + reader.datetime = vector.GenerateFunctionFixedTypeParameter[types.Datetime](v) + case types.T_timestamp: + reader.timestamp = vector.GenerateFunctionFixedTypeParameter[types.Timestamp](v) + case types.T_time: + reader.time = vector.GenerateFunctionFixedTypeParameter[types.Time](v) + case types.T_year: + reader.year = vector.GenerateFunctionFixedTypeParameter[types.MoYear](v) + case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: + default: + return leastGreatestTemporalReader{}, moerr.NewInternalErrorNoCtxf( + "unsupported temporal comparison type %s", v.GetType().Oid.String()) + } + return reader, nil +} + +func (r *leastGreatestTemporalReader) datetimeValue( + row uint64, + temporalItemType types.Type, + proc *process.Process, + loc *time.Location, +) (types.Datetime, error) { + switch r.source.GetType().Oid { + case types.T_date: + val, _ := r.date.GetValue(row) return val.ToDatetime(), nil case types.T_datetime: - p := vector.GenerateFunctionFixedTypeParameter[types.Datetime](v) - val, _ := p.GetValue(row) + val, _ := r.datetime.GetValue(row) return val, nil case types.T_timestamp: - p := vector.GenerateFunctionFixedTypeParameter[types.Timestamp](v) - val, _ := p.GetValue(row) + val, _ := r.timestamp.GetValue(row) return val.ToDatetime(loc), nil case types.T_time: - p := vector.GenerateFunctionFixedTypeParameter[types.Time](v) - val, _ := p.GetValue(row) - return leastGreatestTimeToDatetime(val, v.GetType().Scale, proc, loc), nil + val, _ := r.time.GetValue(row) + return leastGreatestTimeToDatetime(val, r.source.GetType().Scale, proc, loc), nil case types.T_year: - p := vector.GenerateFunctionFixedTypeParameter[types.MoYear](v) - val, _ := p.GetValue(row) + val, _ := r.year.GetValue(row) return types.DatetimeFromClock(int32(val.ToInt64()), 1, 1, 0, 0, 0, 0), nil case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: - return leastGreatestParsePackedDateBytes(v.GetBytesAt(int(row)), temporalItemType, proc, loc) + return leastGreatestParsePackedDateBytes(r.source.GetBytesAt(int(row)), temporalItemType, proc, loc) default: - return types.Datetime(0), moerr.NewInternalErrorNoCtxf("unsupported temporal comparison type %s", v.GetType().Oid.String()) + return types.Datetime(0), moerr.NewInternalErrorNoCtxf( + "unsupported temporal comparison type %s", r.source.GetType().Oid.String()) } } diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index 586976d97a03b..cf2f341e49bb1 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/matrixorigin/matrixone/pkg/container/types" + "github.com/matrixorigin/matrixone/pkg/container/vector" "github.com/matrixorigin/matrixone/pkg/testutil" "github.com/matrixorigin/matrixone/pkg/vm/process" "github.com/stretchr/testify/require" @@ -1247,6 +1248,66 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { require.True(t, ok, info) } +func TestLeastGreatestTemporalAllocationsDoNotScaleWithRows(t *testing.T) { + proc := testutil.NewProcess(t) + + measure := func(rowCount int) float64 { + dates := make([]types.Date, rowCount) + datetimes := make([]types.Datetime, rowCount) + dateVec := newVectorByType(proc.Mp(), types.T_date.ToType(), dates, nil) + datetimeVec := newVectorByType(proc.Mp(), types.T_datetime.ToType(), datetimes, nil) + defer dateVec.Free(proc.Mp()) + defer datetimeVec.Free(proc.Mp()) + + result := vector.NewFunctionResultWrapper(types.T_datetime.ToType(), proc.Mp()) + defer result.Free() + require.NoError(t, result.PreExtendAndReset(rowCount)) + + return testing.AllocsPerRun(5, func() { + if err := result.PreExtendAndReset(rowCount); err != nil { + panic(err) + } + if err := greatestTemporalFn( + []*vector.Vector{dateVec, datetimeVec}, result, proc, rowCount, nil); err != nil { + panic(err) + } + }) + } + + oneRowAllocs := measure(1) + batchAllocs := measure(8192) + require.LessOrEqual(t, batchAllocs, oneRowAllocs+4, + "fixed temporal accessor allocations must be batch-scoped: one row=%v, 8192 rows=%v", + oneRowAllocs, batchAllocs) +} + +func BenchmarkLeastGreatestTemporalFixedReaders(b *testing.B) { + const rowCount = 8192 + proc := testutil.NewProcess(b) + dateVec := newVectorByType(proc.Mp(), types.T_date.ToType(), make([]types.Date, rowCount), nil) + datetimeVec := newVectorByType(proc.Mp(), types.T_datetime.ToType(), make([]types.Datetime, rowCount), nil) + defer dateVec.Free(proc.Mp()) + defer datetimeVec.Free(proc.Mp()) + + result := vector.NewFunctionResultWrapper(types.T_datetime.ToType(), proc.Mp()) + defer result.Free() + if err := result.PreExtendAndReset(rowCount); err != nil { + b.Fatal(err) + } + parameters := []*vector.Vector{dateVec, datetimeVec} + + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := result.PreExtendAndReset(rowCount); err != nil { + b.Fatal(err) + } + if err := greatestTemporalFn(parameters, result, proc, rowCount, nil); err != nil { + b.Fatal(err) + } + } +} + func TestLeastGreatestMixedTemporalExecutorPreservesMaxScale(t *testing.T) { proc := testutil.NewProcess(t) loc := time.FixedZone("UTC", 0) From d3892906ea98ab0524335558fed985f144fc722c Mon Sep 17 00:00:00 2001 From: daviszhen Date: Mon, 20 Jul 2026 10:54:53 +0800 Subject: [PATCH 13/14] update --- .../function/func_builtin_leastgreatest.go | 42 ++++++++--------- .../func_builtin_leastgreatest_test.go | 46 +++++++++++++------ 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest.go b/pkg/sql/plan/function/func_builtin_leastgreatest.go index 1cf3cdb11446b..f0f00c16143d0 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -1068,26 +1068,27 @@ func leastGreatestSetNullResult(result vector.FunctionResultWrapper, length int) type leastGreatestTemporalReader struct { source *vector.Vector - date vector.FunctionParameterWrapper[types.Date] - datetime vector.FunctionParameterWrapper[types.Datetime] - timestamp vector.FunctionParameterWrapper[types.Timestamp] - time vector.FunctionParameterWrapper[types.Time] - year vector.FunctionParameterWrapper[types.MoYear] + isConst bool + date []types.Date + datetime []types.Datetime + timestamp []types.Timestamp + time []types.Time + year []types.MoYear } func newLeastGreatestTemporalReader(v *vector.Vector) (leastGreatestTemporalReader, error) { - reader := leastGreatestTemporalReader{source: v} + reader := leastGreatestTemporalReader{source: v, isConst: v.IsConst()} switch v.GetType().Oid { case types.T_date: - reader.date = vector.GenerateFunctionFixedTypeParameter[types.Date](v) + reader.date = vector.MustFixedColWithTypeCheck[types.Date](v) case types.T_datetime: - reader.datetime = vector.GenerateFunctionFixedTypeParameter[types.Datetime](v) + reader.datetime = vector.MustFixedColWithTypeCheck[types.Datetime](v) case types.T_timestamp: - reader.timestamp = vector.GenerateFunctionFixedTypeParameter[types.Timestamp](v) + reader.timestamp = vector.MustFixedColWithTypeCheck[types.Timestamp](v) case types.T_time: - reader.time = vector.GenerateFunctionFixedTypeParameter[types.Time](v) + reader.time = vector.MustFixedColWithTypeCheck[types.Time](v) case types.T_year: - reader.year = vector.GenerateFunctionFixedTypeParameter[types.MoYear](v) + reader.year = vector.MustFixedColWithTypeCheck[types.MoYear](v) case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: default: return leastGreatestTemporalReader{}, moerr.NewInternalErrorNoCtxf( @@ -1102,22 +1103,21 @@ func (r *leastGreatestTemporalReader) datetimeValue( proc *process.Process, loc *time.Location, ) (types.Datetime, error) { + idx := int(row) + if r.isConst { + idx = 0 + } switch r.source.GetType().Oid { case types.T_date: - val, _ := r.date.GetValue(row) - return val.ToDatetime(), nil + return r.date[idx].ToDatetime(), nil case types.T_datetime: - val, _ := r.datetime.GetValue(row) - return val, nil + return r.datetime[idx], nil case types.T_timestamp: - val, _ := r.timestamp.GetValue(row) - return val.ToDatetime(loc), nil + return r.timestamp[idx].ToDatetime(loc), nil case types.T_time: - val, _ := r.time.GetValue(row) - return leastGreatestTimeToDatetime(val, r.source.GetType().Scale, proc, loc), nil + return leastGreatestTimeToDatetime(r.time[idx], r.source.GetType().Scale, proc, loc), nil case types.T_year: - val, _ := r.year.GetValue(row) - return types.DatetimeFromClock(int32(val.ToInt64()), 1, 1, 0, 0, 0, 0), nil + return types.DatetimeFromClock(int32(r.year[idx].ToInt64()), 1, 1, 0, 0, 0, 0), nil case types.T_char, types.T_varchar, types.T_text, types.T_blob, types.T_binary, types.T_varbinary: return leastGreatestParsePackedDateBytes(r.source.GetBytesAt(int(row)), temporalItemType, proc, loc) default: diff --git a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go index cf2f341e49bb1..e616228b4fd89 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -17,6 +17,7 @@ package function import ( "context" "fmt" + "runtime" "testing" "time" @@ -1225,6 +1226,23 @@ func TestLeastGreatestTemporalExecutor(t *testing.T) { ok, info = tcLeast.Run() require.True(t, ok, info) + dt0, err := types.ParseDatetime("2019-12-31 23:59:59", 0) + require.NoError(t, err) + dt1, err := types.ParseDatetime("2020-01-02 00:00:00", 0) + require.NoError(t, err) + dt2, err := types.ParseDatetime("2020-01-01 12:00:00", 0) + require.NoError(t, err) + tcConstDate := NewFunctionTestCase(proc, + []FunctionTestInput{ + NewFunctionTestConstInput(dateTyp, []types.Date{d1, d1, d1}, nil), + NewFunctionTestInput(types.T_datetime.ToType(), []types.Datetime{dt0, dt1, dt2}, nil), + }, + NewFunctionTestResult(types.T_datetime.ToType(), false, + []types.Datetime{d1.ToDatetime(), dt1, dt2}, nil), + greatestTemporalFn) + ok, info = tcConstDate.Run() + require.True(t, ok, info) + tcInvalidDatePeer := NewFunctionTestCase(proc, []FunctionTestInput{ NewFunctionTestInput(dateTyp, []types.Date{d1}, nil), @@ -1259,25 +1277,27 @@ func TestLeastGreatestTemporalAllocationsDoNotScaleWithRows(t *testing.T) { defer dateVec.Free(proc.Mp()) defer datetimeVec.Free(proc.Mp()) - result := vector.NewFunctionResultWrapper(types.T_datetime.ToType(), proc.Mp()) - defer result.Free() - require.NoError(t, result.PreExtendAndReset(rowCount)) - - return testing.AllocsPerRun(5, func() { - if err := result.PreExtendAndReset(rowCount); err != nil { - panic(err) - } - if err := greatestTemporalFn( - []*vector.Vector{dateVec, datetimeVec}, result, proc, rowCount, nil); err != nil { - panic(err) + parameters := []*vector.Vector{dateVec, datetimeVec} + readers := make([]leastGreatestTemporalReader, len(parameters)) + allocs := testing.AllocsPerRun(1000, func() { + for i, parameter := range parameters { + reader, err := newLeastGreatestTemporalReader(parameter) + if err != nil { + panic(err) + } + readers[i] = reader } }) + runtime.KeepAlive(readers) + return allocs } oneRowAllocs := measure(1) batchAllocs := measure(8192) - require.LessOrEqual(t, batchAllocs, oneRowAllocs+4, - "fixed temporal accessor allocations must be batch-scoped: one row=%v, 8192 rows=%v", + require.Zero(t, oneRowAllocs, + "fixed temporal reader setup must not allocate: one row=%v", oneRowAllocs) + require.Zero(t, batchAllocs, + "fixed temporal reader setup must not scale with rows: one row=%v, 8192 rows=%v", oneRowAllocs, batchAllocs) } From c34b047cec3646a5746bb97a155a058f1f003fb2 Mon Sep 17 00:00:00 2001 From: daviszhen Date: Mon, 20 Jul 2026 11:14:16 +0800 Subject: [PATCH 14/14] update --- pkg/queryservice/multi_cn_bug_test.go | 159 ++++++-------------------- 1 file changed, 35 insertions(+), 124 deletions(-) diff --git a/pkg/queryservice/multi_cn_bug_test.go b/pkg/queryservice/multi_cn_bug_test.go index 25813bee3a68e..7dc291051e94a 100644 --- a/pkg/queryservice/multi_cn_bug_test.go +++ b/pkg/queryservice/multi_cn_bug_test.go @@ -17,7 +17,6 @@ package queryservice import ( "context" "fmt" - "os" "strings" "testing" "time" @@ -25,9 +24,6 @@ import ( "github.com/lni/goutils/leaktest" "github.com/stretchr/testify/assert" - "github.com/matrixorigin/matrixone/pkg/common/morpc" - "github.com/matrixorigin/matrixone/pkg/common/runtime" - "github.com/matrixorigin/matrixone/pkg/defines" "github.com/matrixorigin/matrixone/pkg/pb/metadata" pb "github.com/matrixorigin/matrixone/pkg/pb/query" "github.com/matrixorigin/matrixone/pkg/queryservice/client" @@ -669,133 +665,48 @@ func TestRequestMultipleCn_TimeoutOverrideLogging(t *testing.T) { }) } -// TestRequestMultipleCn_ResponseErrorWithDeadlineExceeded verifies that when a response -// returns with context.DeadlineExceeded error, the code path at query_service.go:202-206 -// is correctly executed. -// -// This test covers the specific code path: -// -// if ctx.Err() == context.DeadlineExceeded || errors.Is(res.err, context.DeadlineExceeded) { -// // Context has timed out, prioritize timeout error -// if retErr == nil { -// retErr = moerr.NewInternalError(ctx, "RequestMultipleCn : context deadline exceeded") -// } -// } -// -// The test ensures: -// 1. SendMessage returns context.DeadlineExceeded error (or ctx.Err() == context.DeadlineExceeded) -// 2. retErr == nil (this is the first error) -// 3. The timeout error is correctly set -// -// Strategy: Create a slow handler that doesn't respond in time, causing context timeout. -// The test waits for RequestMultipleCn to complete with a reasonable timeout. -func TestRequestMultipleCn_ResponseErrorWithDeadlineExceeded(t *testing.T) { - cn := metadata.CNService{ServiceID: "test_response_deadline_exceeded"} - sid := "" - runtime.RunTest( - sid, - func(rt runtime.Runtime) { - runtime.ServiceRuntime(sid).SetGlobalVariables(runtime.MOProtocolVersion, defines.MORPCLatestVersion) - runtime.SetupServiceBasedRuntime(cn.ServiceID, runtime.ServiceRuntime(sid)) - address := fmt.Sprintf("unix:///tmp/cn-%d-%s.sock", - time.Now().Nanosecond(), cn.ServiceID) - - if err := os.RemoveAll(address[7:]); err != nil { - panic(err) - } +type deadlineExceededQueryClient struct{} - qs, err := NewQueryService(cn.ServiceID, address, morpc.Config{}) - assert.NoError(t, err) +var _ client.QueryClient = deadlineExceededQueryClient{} - qt, err := client.NewQueryClient(cn.ServiceID, morpc.Config{}) - assert.NoError(t, err) - - // Event-driven: signal when handler is called - handlerCalled := make(chan struct{}) - - // Handler blocks until context is canceled - qs.AddHandleFunc(pb.CmdMethod_GetCacheInfo, func(ctx context.Context, request *pb.Request, resp *pb.Response, _ *morpc.Buffer) error { - // Signal handler called (non-blocking) - select { - case handlerCalled <- struct{}{}: - default: - } - // Block until context canceled - <-ctx.Done() - return ctx.Err() - }, false) - - err = qs.Start() - assert.NoError(t, err) - defer func() { - err = qs.Close() - assert.NoError(t, err) - err = qt.Close() - assert.NoError(t, err) - }() - - // Long timeout - we'll cancel explicitly after handler called - // This accommodates slow CI (up to 2s) without test failure - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - var successCount int - genRequest := func() *pb.Request { - req := qt.NewRequest(pb.CmdMethod_GetCacheInfo) - req.GetCacheInfoRequest = &pb.GetCacheInfoRequest{} - return req - } +func (deadlineExceededQueryClient) ServiceID() string { return "deadline-exceeded-test" } - handleValidResponse := func(nodeAddr string, rsp *pb.Response) { - if rsp != nil && rsp.GetCacheInfoResponse != nil { - successCount++ - } - } +func (deadlineExceededQueryClient) SendMessage( + context.Context, + string, + *pb.Request, +) (*pb.Response, error) { + return nil, context.DeadlineExceeded +} - // Execute in goroutine - var errResult error - done := make(chan struct{}) - go func() { - errResult = RequestMultipleCn(ctx, []string{address}, qt, genRequest, handleValidResponse, nil) - close(done) - }() - - // Event-driven execution with protection: - // Wait for handler to be called (adapts to CI speed: 10ms - 2s) - select { - case <-handlerCalled: - // Handler called, proceed to cancel - case <-time.After(10 * time.Second): - t.Fatal("Handler not called within 10s - connection issue") - } +func (deadlineExceededQueryClient) NewRequest(method pb.CmdMethod) *pb.Request { + return &pb.Request{CmdMethod: method} +} - // Cancel context immediately (precise control) - cancel() +func (deadlineExceededQueryClient) Release(*pb.Response) {} - // Wait for completion with 10s protection (only for hung) - select { - case <-done: - // Success: fast env ~20ms, slow env ~2s - case <-time.After(10 * time.Second): - t.Fatal("Test hung after context cancel - 10s protection triggered") - } +func (deadlineExceededQueryClient) Close() error { return nil } - // Verify that an error is returned - assert.Error(t, errResult, "Should return error when context deadline exceeded") - // Accept multiple error types that can occur in different environments: - // - "context deadline exceeded": normal timeout path - // - "context canceled": when cancel() is called explicitly - // - "failed to get result": connection error during timeout - // - "EOF": connection closed by server during timeout - // All of these indicate the timeout/cancellation was handled correctly - errStr := errResult.Error() - assert.True(t, - strings.Contains(errStr, "context deadline exceeded") || - strings.Contains(errStr, "context canceled") || - strings.Contains(errStr, "failed to get result") || - strings.Contains(errStr, "EOF"), - "Error should indicate timeout/cancellation or connection error, got: %s", errStr) - assert.Equal(t, 0, successCount, "No nodes should succeed due to timeout") +// TestRequestMultipleCn_ResponseErrorWithDeadlineExceeded verifies that a +// deadline error returned by QueryClient is promoted to the distributed +// request's deadline error, even when the caller context is still active. +func TestRequestMultipleCn_ResponseErrorWithDeadlineExceeded(t *testing.T) { + qt := deadlineExceededQueryClient{} + var successCount int + + err := RequestMultipleCn( + context.Background(), + []string{"deadline-cn"}, + qt, + func() *pb.Request { + return qt.NewRequest(pb.CmdMethod_GetCacheInfo) }, + func(string, *pb.Response) { + successCount++ + }, + nil, ) + + assert.EqualError(t, err, "internal error: RequestMultipleCn : context deadline exceeded") + assert.Zero(t, successCount, "deadline responses must not invoke the valid response handler") }