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..e5387b01e932a --- /dev/null +++ b/pkg/sql/plan/base_binder_leastgreatest_test.go @@ -0,0 +1,120 @@ +// 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/matrixorigin/matrixone/pkg/sql/plan/rule" + "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) + } + }) + } + + 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) { + 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) +} + +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 481f5f3ce3506..1cf3cdb11446b 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest.go @@ -16,26 +16,76 @@ 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 + leastGreatestJSONTemporalOverload + 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 +97,33 @@ 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 + } + + 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 @@ -62,21 +138,424 @@ 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 + } + resolution := leastGreatestPackedDateResolution(inputs, target) + resolution.overloadID = leastGreatestJSONTemporalOverload + return resolution, 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 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, + 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] + } + } + 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 { + 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: + 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: + target := types.T_datetime.ToType() + target.Scale = leastGreatestTemporalMaxScale(inputs) + return target, 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 +688,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 +703,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 +740,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 +753,7 @@ func leastGreatestFnVarlen( for _, pv := range parameters { if pv.IsConstNull() { - nulls.AddRange(rsNull, 0, uint64(length)) - return nil + return leastGreatestSetNullResult(result, length) } } @@ -314,6 +789,442 @@ 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") + } + 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 { + 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) + } + } + + 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 + } + + 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 := 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 := readers[j].datetimeValue(i, resolution.temporalItemType, proc, 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 +} + +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: + 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: + val, _ := r.datetime.GetValue(row) + return val, nil + case types.T_timestamp: + val, _ := r.timestamp.GetValue(row) + return val.ToDatetime(loc), nil + case types.T_time: + val, _ := r.time.GetValue(row) + return leastGreatestTimeToDatetime(val, 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 + 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: + return types.Datetime(0), moerr.NewInternalErrorNoCtxf( + "unsupported temporal comparison type %s", r.source.GetType().Oid.String()) + } +} + +// 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 leastGreatestParsePackedDateBytes(v []byte, temporalItemType types.Type, proc *process.Process, loc *time.Location) (types.Datetime, error) { + s := string(v) + 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()) + } +} + +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 { @@ -325,11 +1236,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: @@ -619,11 +1549,42 @@ 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 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, + 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, length int, selectList *FunctionSelectList) error { + leastGreatestRestoreTemporalResultType(parameters, result) paramType := leastGreatestParamType(parameters) switch paramType.Oid { case types.T_bool: @@ -911,3 +1872,33 @@ 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 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, + 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..cf2f341e49bb1 100644 --- a/pkg/sql/plan/function/func_builtin_leastgreatest_test.go +++ b/pkg/sql/plan/function/func_builtin_leastgreatest_test.go @@ -15,11 +15,15 @@ package function import ( + "context" "fmt" "testing" + "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" ) @@ -552,13 +556,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 +652,941 @@ 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}, + }, + { + 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()}, + 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 { + 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: 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: "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()}, + 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 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) + timeScale6 := types.New(types.T_time, 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) + + 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) { + 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, leastGreatestYearNumericOverload, 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 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") + 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 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) + 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) + + 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) { + 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, tc.wantTarget}, []types.T{targets[0].Oid, targets[1].Oid}) + } + }) + } +} + +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") + 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) + + 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, + []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) +} + +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) + 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 { + datetime, err := types.ParseDatetime(value, scale) + require.NoError(t, err) + return datetime + } + parseTimestamp := func(value string, scale int32) types.Timestamp { + timestamp, err := types.ParseTimestamp(loc, value, scale) + 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 + 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: "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, + 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 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, + 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) +} + +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) + 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/pkg/sql/plan/function/list_builtIn.go b/pkg/sql/plan/function/list_builtIn.go index 1cb2a60f79202..160bdb09935be 100644 --- a/pkg/sql/plan/function/list_builtIn.go +++ b/pkg/sql/plan/function/list_builtIn.go @@ -736,19 +736,39 @@ 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 greatestJSONTemporalFn + }, + }, + { + overloadId: 3, + retType: func(parameters []types.Type) types.Type { + return leastGreatestReturnType(parameters) + }, + newOp: func() executeLogicOfOverload { + return greatestYearNumericFn + }, + }, }, }, @@ -2111,19 +2131,39 @@ 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 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/builtin.result b/test/distributed/cases/function/builtin.result index 5ba8c4b8cacbc..2584d721c8213 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, concat('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; diff --git a/test/distributed/cases/function/greatest_least_numeric.result b/test/distributed/cases/function/greatest_least_numeric.result index 3c88f05b2d9bc..22d6083b0f2ea 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,7 +110,321 @@ 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 +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 argument parsedate, bad value not-a-date +select least(cast('2020-01-01' as date), 'not-a-date'); +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; +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 +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(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 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 +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 +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 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; +➤ 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 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 +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 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 +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) = __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; +➤ 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; +➤ 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, '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 argument parsedate, bad 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 9fb031a77806e..dff9e141ccd1b 100644 --- a/test/distributed/cases/function/greatest_least_numeric.sql +++ b/test/distributed/cases/function/greatest_least_numeric.sql @@ -72,7 +72,170 @@ 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; + +-- 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. 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 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; + +-- 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; + +-- 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 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. +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; +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 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 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 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; +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'); +-- __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(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'); + +-- Unsupported Oids must fail both for same-Oid and mixed calls rather than +-- 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)')); +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, '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; 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;