Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7746508
docs: design fix for issue 24784 time functions
ULookup Jul 16, 2026
09b25ac
docs: add issue 24784 implementation plan
ULookup Jul 16, 2026
85cb2cf
fix: preserve complete time hours
ULookup Jul 16, 2026
10ccc83
fix: preserve time format hours and signs
ULookup Jul 16, 2026
d25fbad
fix: preserve maketime fractional seconds
ULookup Jul 16, 2026
5169d64
fix: reject overflowing maketime hours
ULookup Jul 16, 2026
9f95b7c
test: cover compatible time function semantics
ULookup Jul 16, 2026
6af98ce
docs: fix time function plan formatting
ULookup Jul 16, 2026
bb34a34
fix: reject raw maketime second carry
ULookup Jul 16, 2026
e095ed6
test: preserve decimal maketime scale in expected output
ULookup Jul 16, 2026
17691be
chore: exclude internal planning notes from change
ULookup Jul 16, 2026
2a3913d
test: stabilize maketime BVT output
ULookup Jul 16, 2026
996864c
fix: normalize maketime default scale
ULookup Jul 17, 2026
ec6494c
fix: clamp maketime overflow to endpoint
ULookup Jul 17, 2026
8d55763
fix: normalize maketime float hour overflow
ULookup Jul 17, 2026
1046691
test: cover maketime SQL boundaries
ULookup Jul 17, 2026
cf27d95
fix: validate maketime float minute input
ULookup Jul 17, 2026
23da274
test: format maketime scale regression
ULookup Jul 17, 2026
e03eaf8
fix: align maketime numeric conversion
ULookup Jul 17, 2026
62074fc
fix: preserve maketime string seconds
ULookup Jul 17, 2026
8f00784
fix: preserve maketime string argument semantics
ULookup Jul 17, 2026
62e18ea
Merge remote-tracking branch 'upstream/main' into codex/takeover-2580…
VioletQwQ-0 Jul 20, 2026
3649ba7
fix: close remaining time function compatibility gaps
VioletQwQ-0 Jul 20, 2026
3f329d1
fix(function): preserve binary MAKETIME semantics
VioletQwQ-0 Jul 20, 2026
bb599c6
fix(plan): preserve binary maketime seconds
VioletQwQ-0 Jul 20, 2026
50073bf
fix(function): clamp binary MAKETIME overflow
VioletQwQ-0 Jul 20, 2026
57b33f7
fix: preserve empty time format result
VioletQwQ-0 Jul 20, 2026
8f1ca3a
fix(function): coerce maketime string and binary seconds
VioletQwQ-0 Jul 20, 2026
3e1d50a
fix: preserve decimal maketime rounding precision
VioletQwQ-0 Jul 22, 2026
3513b23
fix datetime review edge cases
VioletQwQ-0 Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions pkg/sql/plan/base_binder.go
Original file line number Diff line number Diff line change
Expand Up @@ -2691,6 +2691,16 @@ func BindFuncExprImplByPlanExpr(ctx context.Context, name string, args []*Expr)
}
}

case "maketime":
// Hex and bit literals are represented as VARCHAR literals carrying
// IsBin. They are integral seconds, so they retain TIME(0) metadata even
// though the VARCHAR seconds overload normally advertises TIME(6).
if len(args) == 3 {
if literal := args[2].GetLit(); literal != nil && literal.IsBin {
returnType.Scale = 0
}
}

case "timestampadd":
// For TIMESTAMPADD with DATE input, check if unit is constant and adjust return type
// MySQL behavior: DATE input + date unit → DATE output, DATE input + time unit → DATETIME output
Expand Down Expand Up @@ -2741,6 +2751,15 @@ func BindFuncExprImplByPlanExpr(ctx context.Context, name string, args []*Expr)
}
for idx, castType := range argsCastType {
if !argsType[idx].Eq(castType) && castType.Oid != types.T_any {
// MAKETIME uses the scale on its VARCHAR seconds target only to
// derive the TIME return scale. Recasting an already-VARCHAR
// argument solely for that metadata clears Literal.IsBin, changing
// X'..'/B'..' from a binary number into ordinary text.
if name == "maketime" && idx == 2 &&
argsType[idx].Oid == types.T_varchar && castType.Oid == types.T_varchar &&
argsType[idx].Width == castType.Width {
continue
}
if argsType[idx].Oid == castType.Oid && castType.Oid.IsDecimal() && argsType[idx].Scale == castType.Scale {
continue
}
Expand Down
102 changes: 102 additions & 0 deletions pkg/sql/plan/build_expr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package plan

import (
"strings"
"testing"
"time"

Expand Down Expand Up @@ -402,6 +403,107 @@ func runOneExprStmt(opt Optimizer, t *testing.T, sql string) (*plan.Plan, error)
return pl, nil
}

func TestMakeTimeBinaryLiteralBindAndExecute(t *testing.T) {
tests := []struct {
name string
sql string
want string
wantNull bool
}{
{name: "hex hour", sql: "select cast(maketime(X'0102', 0, 0) as varchar)", want: "258:00:00"},
{name: "hex hour empty", sql: "select cast(maketime(X'', 0, 0) as varchar)", want: "00:00:00"},
{name: "hex hour max int64", sql: "select cast(maketime(X'7FFFFFFFFFFFFFFF', 0, 0) as varchar)", want: "838:59:59"},
{name: "hex hour max int64 plus one", sql: "select cast(maketime(X'8000000000000000', 0, 0) as varchar)", want: "838:59:59"},
{name: "hex hour uint64 overflow", sql: "select cast(maketime(X'FFFFFFFFFFFFFFFF', 0, 0) as varchar)", want: "838:59:59"},
{name: "hex hour wider than uint64", sql: "select cast(maketime(X'FFFFFFFFFFFFFFFFFF', 0, 0) as varchar)", want: "838:59:59"},
{name: "hex hour wide leading zeros", sql: "select cast(maketime(X'000000000000000001', 0, 0) as varchar)", want: "01:00:00"},
{name: "hex minute", sql: "select cast(maketime(12, X'01', 0) as varchar)", want: "12:01:00"},
{name: "hex minute overflow", sql: "select cast(maketime(12, X'FFFFFFFFFFFFFFFFFF', 0) as varchar)", wantNull: true},
{name: "hex minute wide leading zeros", sql: "select cast(maketime(12, X'000000000000000001', 0) as varchar)", want: "12:01:00"},
{name: "hex second", sql: "select cast(maketime(12, 0, X'01') as varchar)", want: "12:00:01"},
{name: "hex second wide leading zeros", sql: "select cast(maketime(12, 0, X'000000000000000001') as varchar)", want: "12:00:01"},
{name: "hex second wide leading zero max", sql: "select cast(maketime(12, 0, X'00000000000000003B') as varchar)", want: "12:00:59"},
{name: "hex second wider overflow", sql: "select cast(maketime(12, 0, X'010000000000000000') as varchar)", wantNull: true},
{name: "bit second", sql: "select cast(maketime(12, 0, B'00000001') as varchar)", want: "12:00:01"},
{name: "binary string second", sql: "select cast(maketime(12, 0, cast('01' as binary(2))) as varchar)", want: "12:00:01"},
{name: "empty string second coerces to zero", sql: "select cast(maketime(12, 34, '') as varchar)", want: "12:34:00.000000"},
{name: "nonnumeric string second coerces to zero", sql: "select cast(maketime(12, 34, 'foo') as varchar)", want: "12:34:00.000000"},
{name: "plain strings", sql: "select cast(maketime('12.7', '15.8', '30.9') as varchar)", want: "12:15:30.900000"},
{name: "decimal second", sql: "select cast(maketime(12, 34, cast('56.789012' as decimal(20, 6))) as varchar)", want: "12:34:56.789012"},
{name: "decimal minute below half", sql: "select cast(maketime(12, cast('59.49999999999999999999' as decimal(30, 20)), cast('0' as decimal(2, 1))) as varchar)", want: "12:59:00.0"},
{name: "decimal minute at half", sql: "select cast(maketime(12, cast('58.5' as decimal(3, 1)), 0) as varchar)", want: "12:59:00"},
{name: "decimal64 minute below half", sql: "select cast(maketime(12, cast('58.499999999999999' as decimal(17, 15)), 0) as varchar)", want: "12:58:00"},
{name: "decimal minute rounds out of range", sql: "select maketime(12, cast('59.5' as decimal(3, 1)), 0)", wantNull: true},
{name: "decimal hour below half", sql: "select cast(maketime(cast('12.49999999999999999999' as decimal(30, 20)), 0, 0) as varchar)", want: "12:00:00"},
{name: "negative decimal hour at half", sql: "select cast(maketime(cast('-12.5' as decimal(3, 1)), 0, 0) as varchar)", want: "-13:00:00"},
{name: "decimal hour positive overflow", sql: "select cast(maketime(cast('99999999999999999999999999999999999999' as decimal(38, 0)), 0, 0) as varchar)", want: "838:59:59"},
{name: "decimal hour negative overflow", sql: "select cast(maketime(cast('-99999999999999999999999999999999999999' as decimal(38, 0)), 0, 0) as varchar)", want: "-838:59:59"},
{name: "decimal256 hour below half", sql: "select cast(maketime(cast('12.499999999999999999999999999999' as decimal(65, 30)), 0, 0) as varchar)", want: "12:00:00"},
{name: "decimal256 hour at half", sql: "select cast(maketime(cast('12.500000000000000000000000000000' as decimal(65, 30)), 0, 0) as varchar)", want: "13:00:00"},
{name: "decimal256 minute below half", sql: "select cast(maketime(12, cast('58.499999999999999999999999999999' as decimal(65, 30)), 0) as varchar)", want: "12:58:00"},
{name: "decimal256 minute at half", sql: "select cast(maketime(12, cast('58.500000000000000000000000000000' as decimal(65, 30)), 0) as varchar)", want: "12:59:00"},
{name: "decimal256 hour positive overflow", sql: "select cast(maketime(cast('99999999999999999999999999999999999999999999999999999999999999999' as decimal(65, 0)), 0, 0) as varchar)", want: "838:59:59"},
{name: "decimal256 hour negative overflow", sql: "select cast(maketime(cast('-99999999999999999999999999999999999999999999999999999999999999999' as decimal(65, 0)), 0, 0) as varchar)", want: "-838:59:59"},
{name: "safe exponent underflow", sql: "select cast(maketime(12, 34, '1e-5000') as varchar)", want: "12:34:00.000000"},
{name: "zero mantissa huge exponent", sql: "select cast(maketime(12, 34, '0e5000') as varchar)", want: "12:34:00.000000"},
{name: "wide zero mantissa", sql: "select cast(maketime(12, 34, '" + strings.Repeat("0", 4097) + "') as varchar)", want: "12:34:00.000000"},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mock := NewMockOptimizer(false)
pl, err := runOneExprStmt(mock, t, test.sql)
require.NoError(t, err)

query := pl.GetQuery()
require.NotNil(t, query)
expr := query.Nodes[1].ProjectList[0]
proc := testutil.NewProc(t)
defer proc.Free()
executor, err := colexec.NewExpressionExecutor(proc, expr)
require.NoError(t, err)
defer executor.Free()

result, err := executor.Eval(proc, nil, nil)
require.NoError(t, err)
if test.wantNull {
require.True(t, result.GetNulls().Contains(0))
return
}
require.False(t, result.GetNulls().Contains(0))
require.Equal(t, test.want, result.GetStringAt(0))
})
}
}

func TestMakeTimeExtremeExactSecondBindAndExecute(t *testing.T) {
tests := []struct {
name string
sql string
}{
{"exponent", "select maketime(12, 34, '1e" + strings.Repeat("9", 8192) + "')"},
{"mantissa", "select maketime(12, 34, '" + strings.Repeat("9", 4097) + "')"},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mock := NewMockOptimizer(false)
pl, err := runOneExprStmt(mock, t, test.sql)
require.NoError(t, err)

expr := pl.GetQuery().Nodes[1].ProjectList[0]
proc := testutil.NewProc(t)
defer proc.Free()
executor, err := colexec.NewExpressionExecutor(proc, expr)
require.NoError(t, err)
defer executor.Free()
result, err := executor.Eval(proc, nil, nil)
require.NoError(t, err)
require.True(t, result.GetNulls().Contains(0))
})
}
}

func makeTimeExpr(s string, p int32) *plan.Expr {
dt, _ := types.ParseTime(s, 0)
return &plan.Expr{
Expand Down
Loading
Loading