Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a205865
fix 1
XuPeng-SH Nov 26, 2025
292fe40
fix 2
XuPeng-SH Nov 26, 2025
c06b8d7
fix 3
XuPeng-SH Nov 26, 2025
7d08b09
fix 4
XuPeng-SH Nov 26, 2025
e3094aa
fix 5
XuPeng-SH Nov 26, 2025
04d3df3
fix 6
XuPeng-SH Nov 26, 2025
569533b
fix 7
XuPeng-SH Nov 26, 2025
f0f9382
fix 8
XuPeng-SH Nov 26, 2025
c06ba7d
fix 9
XuPeng-SH Nov 26, 2025
37b0559
fix 10
XuPeng-SH Nov 26, 2025
00c1eb5
Merge remote-tracking branch 'origin/main' into fix-time-related-2
XuPeng-SH Nov 27, 2025
3632ed2
fix 10
XuPeng-SH Nov 27, 2025
a55edf8
fix 11
XuPeng-SH Nov 27, 2025
8aff06c
fix 12
XuPeng-SH Nov 27, 2025
a815d65
Merge branch 'main' into fix-time-related-2
XuPeng-SH Nov 27, 2025
be3a745
fix wip
XuPeng-SH Nov 27, 2025
8d4a7ca
fix wip
XuPeng-SH Nov 27, 2025
63ac42e
fix
XuPeng-SH Nov 27, 2025
cc00975
rm protocol changes
XuPeng-SH Nov 27, 2025
0c85cc7
fix ut
XuPeng-SH Nov 28, 2025
a151aba
fix bvt
XuPeng-SH Nov 28, 2025
109d9af
fix bvt 2
XuPeng-SH Nov 28, 2025
07f8aa2
fix
XuPeng-SH Nov 28, 2025
77cc01c
Merge main: resolve conflicts in mysql_sql.go by regenerating from my…
XuPeng-SH Nov 28, 2025
de28c3b
Merge remote-tracking branch 'origin/main' into fix-time-related-2
XuPeng-SH Nov 28, 2025
b2b9052
update
XuPeng-SH Nov 29, 2025
1f02c5c
update 1
XuPeng-SH Nov 29, 2025
1b1853b
update 2
XuPeng-SH Nov 29, 2025
81408ed
update 3
XuPeng-SH Nov 29, 2025
fd598ec
update 4
XuPeng-SH Nov 29, 2025
803584d
update 5
XuPeng-SH Nov 29, 2025
d5960f1
update 6
XuPeng-SH Nov 29, 2025
2a4f109
update 7
XuPeng-SH Nov 29, 2025
91c6401
update 8
XuPeng-SH Nov 29, 2025
0e42f24
Merge branch 'main' into fix-time-related-2
XuPeng-SH Nov 29, 2025
ef97a53
fix sca
XuPeng-SH Nov 29, 2025
a48f801
update
XuPeng-SH Nov 29, 2025
3076697
update
XuPeng-SH Nov 29, 2025
6d257a6
update
XuPeng-SH Nov 29, 2025
7a71e74
update
XuPeng-SH Nov 29, 2025
4986514
update
XuPeng-SH Nov 29, 2025
eae8831
update
XuPeng-SH Nov 29, 2025
87e52a0
update rr
XuPeng-SH Nov 29, 2025
da5d652
update rr2
XuPeng-SH Nov 29, 2025
2664cb4
fix
XuPeng-SH Nov 30, 2025
219ec03
fix sca
XuPeng-SH Nov 30, 2025
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
11 changes: 11 additions & 0 deletions pkg/container/types/date.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,10 +579,21 @@ func (d Date) Calendar(full bool) (year int32, month, day uint8, yday uint16) {
// Estimate month on assumption that every month has 31 days.
// The estimate may be too low by at most one month, so adjust.
month = uint8(d / 31)
// Check bounds: daysBefore array has 13 elements (0-12), so month+1 must be <= 12
// If month is too large, the date is invalid (out of valid datetime range)
if month+1 >= uint8(len(daysBefore)) {
// Return invalid date (year=0) to indicate invalid date
// ValidDatetime will catch this and return false
return 0, 0, 0, 0
}
end := daysBefore[month+1]
var begin uint16
if uint16(d) >= end {
month++
// Check bounds again after increment
if month+1 >= uint8(len(daysBefore)) {
return 0, 0, 0, 0
}
begin = end
} else {
begin = daysBefore[month]
Expand Down
20 changes: 19 additions & 1 deletion pkg/container/types/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (dt Datetime) String() string {
func (dt Datetime) String2(scale int32) string {
y, m, d, _ := dt.ToDate().Calendar(true)
hour, minute, sec := dt.Clock()

if scale > 0 {
msec := int64(dt) % MicroSecsPerSec
// Format microseconds as 6 digits (max precision we store)
Expand Down Expand Up @@ -119,7 +120,13 @@ func ParseDatetime(s string, scale int32) (Datetime, error) {
if s[4] == '-' || s[4] == '/' || s[4] == ':' {
var num int64
var unum uint64
strArr := strings.Split(s, " ")
// Support both space-separated format (2024-12-20 10:30:45) and ISO 8601 format (2024-12-20T10:30:45)
var strArr []string
if strings.Contains(s, "T") {
strArr = strings.Split(s, "T")
} else {
strArr = strings.Split(s, " ")
}
if len(strArr) != 2 {
return -1, moerr.NewInvalidInputNoCtxf("invalid datetime value %s", s)
}
Expand Down Expand Up @@ -413,6 +420,12 @@ func (dt Datetime) AddDateTime(addMonth, addYear int64, timeType TimeType) (Date
func (dt Datetime) AddInterval(nums int64, its IntervalType, timeType TimeType) (Datetime, bool) {
var addMonth, addYear int64
switch its {
case MicroSecond:
// nums is already in microseconds, no conversion needed
// For time units (MicroSecond, Second, Minute, Hour), the addition won't change the date part
// so we can directly return the result without ValidDatetime check
newDate := dt + Datetime(nums)
return newDate, true
case Second:
nums *= MicroSecsPerSec
case Minute:
Expand All @@ -432,6 +445,11 @@ func (dt Datetime) AddInterval(nums int64, its IntervalType, timeType TimeType)
case Year:
addYear = nums
return dt.AddDateTime(addMonth, addYear, timeType)
case Year_Month:
// Year_Month should be treated as Month (nums already represents months after NormalizeInterval)
// This handles the case where Year_Month type is directly passed without NormalizeInterval conversion
addMonth = nums
return dt.AddDateTime(addMonth, addYear, timeType)
}

newDate := dt + Datetime(nums)
Expand Down
69 changes: 69 additions & 0 deletions pkg/container/types/datetime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,37 @@ func TestParseDatetime(t *testing.T) {
args: "2000:01:01 12:34:56.123456",
want: "2000-01-01 12:34:56.123456",
},
// 6. ISO 8601 format (yyyy-mm-ddThh:mm:ss)
{
name: "ISO 8601 format yyyy-mm-ddThh:mm:ss",
args: "2024-12-20T10:30:45",
want: "2024-12-20 10:30:45.000000",
},
{
name: "ISO 8601 format with microseconds",
args: "2024-12-20T10:30:45.123456",
want: "2024-12-20 10:30:45.123456",
},
{
name: "ISO 8601 format with milliseconds",
args: "2024-12-20T10:30:45.123",
want: "2024-12-20 10:30:45.123000",
},
{
name: "ISO 8601 format with single digit microseconds",
args: "2024-12-20T10:30:45.1",
want: "2024-12-20 10:30:45.100000",
},
{
name: "ISO 8601 format midnight",
args: "2024-12-20T00:00:00",
want: "2024-12-20 00:00:00.000000",
},
{
name: "ISO 8601 format end of day",
args: "2024-12-20T23:59:59",
want: "2024-12-20 23:59:59.000000",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -562,3 +593,41 @@ func TestDatetime_ToTime(t *testing.T) {
resultTime := resultDt.ToTime(9)
require.Equal(t, "13:46:15.000000", resultTime.String2(6), "TIME(ADDTIME(...)) should work correctly")
}

// TestAddIntervalMicrosecond tests AddInterval with MicroSecond unit
// This test verifies the fix for TIMESTAMPADD(MICROSECOND, 1000000, DATE('2024-12-20'))
func TestAddIntervalMicrosecond(t *testing.T) {
// Test case: DATE('2024-12-20') + 1000000 microseconds = 2024-12-20 00:00:01.000000
date, _ := ParseDateCast("2024-12-20")
dt := date.ToDatetime()

// Add 1000000 microseconds (1 second)
result, success := dt.AddInterval(1000000, MicroSecond, DateTimeType)
require.True(t, success, "AddInterval should succeed for MicroSecond")
require.NotEqual(t, Datetime(0), result, "Result should not be zero")

// Verify the result is 2024-12-20 00:00:01.000000
expected, _ := ParseDatetime("2024-12-20 00:00:01.000000", 6)
require.Equal(t, expected, result, "DATE + 1000000 microseconds should equal 2024-12-20 00:00:01.000000")

// Verify the string representation
require.Equal(t, "2024-12-20 00:00:01.000000", result.String2(6), "String representation should match")

// Test with different microsecond values
testCases := []struct {
microseconds int64
expected string
}{
{1000000, "2024-12-20 00:00:01.000000"}, // 1 second
{500000, "2024-12-20 00:00:00.500000"}, // 0.5 seconds
{123456, "2024-12-20 00:00:00.123456"}, // 123456 microseconds
{2000000, "2024-12-20 00:00:02.000000"}, // 2 seconds
}

for _, tc := range testCases {
result, success := dt.AddInterval(tc.microseconds, MicroSecond, DateTimeType)
require.True(t, success, "AddInterval should succeed for %d microseconds", tc.microseconds)
require.NotEqual(t, Datetime(0), result, "Result should not be zero for %d microseconds", tc.microseconds)
require.Equal(t, tc.expected, result.String2(6), "Result should match expected for %d microseconds", tc.microseconds)
}
}
51 changes: 50 additions & 1 deletion pkg/container/types/interval.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,26 @@ func parseInts(s string, isxxxMicrosecond bool, typeMaxLength int) ([]int64, err
}
}
if isxxxMicrosecond {
if len(ret) == typeMaxLength {
// For microsecond types, the last number represents fractional seconds
// If we have fewer values than expected, we need to handle the fractional part
if len(ret) > 0 && len(ret) < typeMaxLength {
// The last value is a fractional part (e.g., "1.02" -> [1, 2] where 2 means 0.02)
// We need to convert it to microseconds and pad missing values with 0
lastIdx := len(ret) - 1
lastNumLength := 0
// Count digits in the last number by finding the last number in the string
for i := len(s) - 1; i >= 0; i-- {
if s[i] >= '0' && s[i] <= '9' {
lastNumLength++
} else if lastNumLength > 0 {
break
}
}
// Convert fractional part to microseconds (e.g., 2 -> 20000 for 0.02 seconds)
if lastNumLength > 0 {
ret[lastIdx] *= int64(math.Pow10(6 - lastNumLength))
}
} else if len(ret) == typeMaxLength {
ret[len(ret)-1] *= int64(math.Pow10(6 - numLength))
}
}
Expand Down Expand Up @@ -202,6 +221,36 @@ func NormalizeInterval(s string, it IntervalType) (ret int64, rettype IntervalTy
return
}

// For composite interval types, if we have fewer values than expected,
// pad with zeros. The interpretation depends on the interval type:
// - For microsecond types (Day_MicroSecond, Hour_MicroSecond, etc.):
// If we have 2 values, the first is the second-to-last unit (e.g., second for day_microsecond),
// and the last is the microsecond part.
// - For other types, pad from the left (missing higher-order units default to 0)
typeMaxLen := typeMaxLength(it)
if len(vals) < typeMaxLen && typeMaxLen > 1 {
padded := make([]int64, typeMaxLen)
if isxxxMicrosecondType(it) {
// For microsecond types, if we have exactly 2 values, they represent
// the second-to-last unit and the microsecond part
// e.g., '1.02' day_microsecond -> [1, 20000] -> [0, 0, 0, 1, 20000]
if len(vals) == 2 {
padded[typeMaxLen-2] = vals[0] // second-to-last position
padded[typeMaxLen-1] = vals[1] // last position (microsecond)
} else if len(vals) == 1 {
// Single value goes to the last position (microsecond)
padded[typeMaxLen-1] = vals[0]
} else {
// More than 2 values: pad from the left
copy(padded[typeMaxLen-len(vals):], vals)
}
} else {
// For non-microsecond types, pad from the left
copy(padded[typeMaxLen-len(vals):], vals)
}
vals = padded
}

switch it {
case MicroSecond, Second, Minute, Hour, Day,
Week, Month, Quarter, Year:
Expand Down
10 changes: 6 additions & 4 deletions pkg/container/types/interval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,17 @@ func TestConv(t *testing.T) {
require.Equal(t, vt, Minute, "HM error")
require.Equal(t, err, nil, "HM error")

// MySQL behavior: empty string is treated as 0, no error
val, vt, err = NormalizeInterval("", Hour_Minute)
require.Equal(t, val, int64(0), "HM error")
require.Equal(t, vt, IntervalTypeInvalid, "HM error")
require.NotEqual(t, err, nil, "HM error")
require.Equal(t, vt, Minute, "HM error")
require.Equal(t, err, nil, "HM error")

// MySQL behavior: invalid string is treated as 0, no error
val, vt, err = NormalizeInterval("foo", Hour_Minute)
require.Equal(t, val, int64(0), "HM error")
require.Equal(t, vt, IntervalTypeInvalid, "HM error")
require.NotEqual(t, err, nil, "HM error")
require.Equal(t, vt, Minute, "HM error")
require.Equal(t, err, nil, "HM error")

val, vt, err = NormalizeInterval("1 01:02:03.4", Day_MicroSecond)
val2, vt2, _ := NormalizeInterval("1 01:02:03.0", Day_MicroSecond)
Expand Down
14 changes: 9 additions & 5 deletions pkg/container/vector/vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -2740,18 +2740,22 @@ func implDatetimeRowToString(v *Vector, idx int) string {
return "null"
}

var dt types.Datetime
if v.IsConst() {
if nulls.Contains(&v.nsp, 0) {
return "null"
} else {
return GetFixedAtNoTypeCheck[types.Datetime](v, 0).String2(v.typ.Scale)
dt = GetFixedAtNoTypeCheck[types.Datetime](v, 0)
}
}
if v.nsp.Contains(uint64(idx)) {
return "null"
} else {
return GetFixedAtNoTypeCheck[types.Datetime](v, idx).String2(v.typ.Scale)
if v.nsp.Contains(uint64(idx)) {
return "null"
} else {
dt = GetFixedAtNoTypeCheck[types.Datetime](v, idx)
}
}

return dt.String2(v.typ.Scale)
}

func implDecimalRowToString[T types.DecimalWithFormat](v *Vector, idx int) string {
Expand Down
83 changes: 83 additions & 0 deletions pkg/container/vector/vector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2837,13 +2837,96 @@ func TestRowToString(t *testing.T) {
require.Equal(t, int64(0), mp.CurrNB())
}
{ // datetime
// Test 1: Non-const vector with non-null value
v := NewVec(types.T_datetime.ToType())
scale := types.Datetime(types.MicroSecsPerSec * types.SecsPerDay)
err := AppendFixedList(v, []types.Datetime{1 * scale, 2 * scale, 3 * scale, 4 * scale}, nil, mp)
require.NoError(t, err)
require.Equal(t, "0001-01-03 00:00:00", v.RowToString(1))
v.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 2: Non-const vector with null value
v2 := NewVec(types.T_datetime.ToType())
err = AppendFixedList(v2, []types.Datetime{1 * scale, 2 * scale, 3 * scale, 4 * scale}, []bool{false, false, true, false}, mp)
require.NoError(t, err)
require.Equal(t, "null", v2.RowToString(2))
v2.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 3: Const null vector
v3 := NewConstNull(types.T_datetime.ToType(), 1, mp)
require.Equal(t, "null", v3.RowToString(0))
v3.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 4: Const vector with null
v4, err := NewConstFixed(types.T_datetime.ToType(), 1*scale, 1, mp)
require.NoError(t, err)
nulls.Add(&v4.nsp, 0)
require.Equal(t, "null", v4.RowToString(0))
v4.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 5: Const vector with non-null value
v5, err := NewConstFixed(types.T_datetime.ToType(), 2*scale, 1, mp)
require.NoError(t, err)
require.Equal(t, "0001-01-03 00:00:00", v5.RowToString(0))
v5.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 6: Non-const vector with different scale
v6 := NewVec(types.T_datetime.ToType())
v6.SetTypeScale(3)
err = AppendFixedList(v6, []types.Datetime{1 * scale, 2 * scale}, nil, mp)
require.NoError(t, err)
// The output should include microseconds with scale 3
result := v6.RowToString(0)
require.Contains(t, result, "0001-01-02")
v6.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 7: Const vector with different scale
v7, err := NewConstFixed(types.T_datetime.ToType(), 2*scale, 1, mp)
require.NoError(t, err)
v7.SetTypeScale(6)
result2 := v7.RowToString(0)
require.Contains(t, result2, "0001-01-03")
v7.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 8: Non-const vector with null at index 0
v8 := NewVec(types.T_datetime.ToType())
err = AppendFixedList(v8, []types.Datetime{1 * scale, 2 * scale}, []bool{true, false}, mp)
require.NoError(t, err)
require.Equal(t, "null", v8.RowToString(0))
require.Equal(t, "0001-01-03 00:00:00", v8.RowToString(1))
v8.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 9: Ensure all code paths are covered - const vector with nulls.Add but not const null
// This tests the else branch at line 2747-2748 when const is true but not IsConstNull
v9, err := NewConstFixed(types.T_datetime.ToType(), 3*scale, 1, mp)
require.NoError(t, err)
// Ensure it's not const null (has data)
require.False(t, v9.IsConstNull())
// Ensure nsp doesn't contain 0 (line 2745 check should be false)
require.False(t, nulls.Contains(&v9.nsp, 0))
result9 := v9.RowToString(0)
require.Contains(t, result9, "0001-01-04")
v9.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())

// Test 10: Ensure all code paths are covered - non-const vector else branch at line 2753-2754
v10 := NewVec(types.T_datetime.ToType())
err = AppendFixedList(v10, []types.Datetime{5 * scale, 6 * scale}, []bool{false, false}, mp)
require.NoError(t, err)
// Ensure idx 0 is not null (line 2751 check should be false)
require.False(t, v10.nsp.Contains(0))
result10 := v10.RowToString(0)
require.Contains(t, result10, "0001-01-06")
v10.Free(mp)
require.Equal(t, int64(0), mp.CurrNB())
}
{ // time
v := NewVec(types.T_time.ToType())
Expand Down
Loading
Loading