Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

types: refine the parsing logic of INTERVAL to correct DATE_ADD/DATE_SUB (#9874) #9963

Merged
merged 1 commit into from
Mar 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 5 additions & 5 deletions expression/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1770,11 +1770,11 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) {

{"\"2011-11-11\"", "\"abc1000\"", "MICROSECOND", "<nil>", "<nil>"},
{"\"20111111 10:10:10\"", "\"1\"", "DAY", "<nil>", "<nil>"},
{"\"2011-11-11\"", "\"10\"", "SECOND_MICROSECOND", "<nil>", "<nil>"},
{"\"2011-11-11\"", "\"10.0000\"", "MINUTE_MICROSECOND", "<nil>", "<nil>"},
{"\"2011-11-11\"", "\"10:10:10\"", "MINUTE_MICROSECOND", "<nil>", "<nil>"},
{"\"2011-11-11\"", "\"10\"", "SECOND_MICROSECOND", "2011-11-11 00:00:00.100000", "2011-11-10 23:59:59.900000"},
{"\"2011-11-11\"", "\"10.0000\"", "MINUTE_MICROSECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"},
{"\"2011-11-11\"", "\"10:10:10\"", "MINUTE_MICROSECOND", "2011-11-11 00:10:10.100000", "2011-11-10 23:49:49.900000"},

{"cast(\"2011-11-11\" as datetime)", "\"10:10:10\"", "MINUTE_MICROSECOND", "<nil>", "<nil>"},
{"cast(\"2011-11-11\" as datetime)", "\"10:10:10\"", "MINUTE_MICROSECOND", "2011-11-11 00:10:10.100000", "2011-11-10 23:49:49.900000"},
{"cast(\"2011-11-11 00:00:00\" as datetime)", "1", "DAY", "2011-11-12 00:00:00", "2011-11-10 00:00:00"},
{"cast(\"2011-11-11 00:00:00\" as datetime)", "10", "HOUR", "2011-11-11 10:00:00", "2011-11-10 14:00:00"},
{"cast(\"2011-11-11 00:00:00\" as datetime)", "10", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"},
Expand All @@ -1785,7 +1785,7 @@ func (s *testIntegrationSuite) TestTimeBuiltin(c *C) {
{"cast(\"2011-11-11 00:00:00\" as datetime)", "\"10\"", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"},
{"cast(\"2011-11-11 00:00:00\" as datetime)", "\"10\"", "SECOND", "2011-11-11 00:00:10", "2011-11-10 23:59:50"},

{"cast(\"2011-11-11\" as date)", "\"10:10:10\"", "MINUTE_MICROSECOND", "<nil>", "<nil>"},
{"cast(\"2011-11-11\" as date)", "\"10:10:10\"", "MINUTE_MICROSECOND", "2011-11-11 00:10:10.100000", "2011-11-10 23:49:49.900000"},
{"cast(\"2011-11-11 00:00:00\" as date)", "1", "DAY", "2011-11-12", "2011-11-10"},
{"cast(\"2011-11-11 00:00:00\" as date)", "10", "HOUR", "2011-11-11 10:00:00", "2011-11-10 14:00:00"},
{"cast(\"2011-11-11 00:00:00\" as date)", "10", "MINUTE", "2011-11-11 00:10:00", "2011-11-10 23:50:00"},
Expand Down
316 changes: 98 additions & 218 deletions types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,50 @@ const (
TimeMaxValueSeconds = TimeMaxHour*3600 + TimeMaxMinute*60 + TimeMaxSecond
)

const (
// YearIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
YearIndex = 0 + iota
// MonthIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
MonthIndex
// DayIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
DayIndex
// HourIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
HourIndex
// MinuteIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
MinuteIndex
// SecondIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
SecondIndex
// MicrosecondIndex is index of 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
MicrosecondIndex
)

const (
// YearMonthMaxCnt is max parameters count 'YEARS-MONTHS' expr Format allowed
YearMonthMaxCnt = 2
// DayHourMaxCnt is max parameters count 'DAYS HOURS' expr Format allowed
DayHourMaxCnt = 2
// DayMinuteMaxCnt is max parameters count 'DAYS HOURS:MINUTES' expr Format allowed
DayMinuteMaxCnt = 3
// DaySecondMaxCnt is max parameters count 'DAYS HOURS:MINUTES:SECONDS' expr Format allowed
DaySecondMaxCnt = 4
// DayMicrosecondMaxCnt is max parameters count 'DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format allowed
DayMicrosecondMaxCnt = 5
// HourMinuteMaxCnt is max parameters count 'HOURS:MINUTES' expr Format allowed
HourMinuteMaxCnt = 2
// HourSecondMaxCnt is max parameters count 'HOURS:MINUTES:SECONDS' expr Format allowed
HourSecondMaxCnt = 3
// HourMicrosecondMaxCnt is max parameters count 'HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format allowed
HourMicrosecondMaxCnt = 4
// MinuteSecondMaxCnt is max parameters count 'MINUTES:SECONDS' expr Format allowed
MinuteSecondMaxCnt = 2
// MinuteMicrosecondMaxCnt is max parameters count 'MINUTES:SECONDS.MICROSECONDS' expr Format allowed
MinuteMicrosecondMaxCnt = 3
// SecondMicrosecondMaxCnt is max parameters count 'SECONDS.MICROSECONDS' expr Format allowed
SecondMicrosecondMaxCnt = 2
// TimeValueCnt is parameters count 'YEARS-MONTHS DAYS HOURS:MINUTES:SECONDS.MICROSECONDS' expr Format
TimeValueCnt = 7
)

// Zero values for different types.
var (
// ZeroDuration is the zero value for Duration type.
Expand Down Expand Up @@ -1555,234 +1599,67 @@ func extractSingleTimeValue(unit string, format string) (int64, int64, int64, fl
return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit)
}

// extractSecondMicrosecond extracts second and microsecond from a string and its format is `SS.FFFFFF`.
func extractSecondMicrosecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, ".")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

seconds, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

microseconds, err := strconv.ParseFloat(alignFrac(fields[1], MaxFsp), 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

return 0, 0, 0, seconds*float64(gotime.Second) + microseconds*float64(gotime.Microsecond), nil
}

// extractMinuteMicrosecond extracts minutes and microsecond from a string and its format is `MM:SS.FFFFFF`.
func extractMinuteMicrosecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, ":")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

minutes, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

_, _, _, value, err := extractSecondMicrosecond(fields[1])
if err != nil {
return 0, 0, 0, 0, errors.Trace(err)
}

return 0, 0, 0, minutes*float64(gotime.Minute) + value, nil
}

// extractMinuteSecond extracts minutes and second from a string and its format is `MM:SS`.
func extractMinuteSecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, ":")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

minutes, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

seconds, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

return 0, 0, 0, minutes*float64(gotime.Minute) + seconds*float64(gotime.Second), nil
}

// extractHourMicrosecond extracts hour and microsecond from a string and its format is `HH:MM:SS.FFFFFF`.
func extractHourMicrosecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, ":")
if len(fields) != 3 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

hours, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

minutes, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

_, _, _, value, err := extractSecondMicrosecond(fields[2])
if err != nil {
return 0, 0, 0, 0, errors.Trace(err)
}

return 0, 0, 0, hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) + value, nil
}

// extractHourSecond extracts hour and second from a string and its format is `HH:MM:SS`.
func extractHourSecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, ":")
if len(fields) != 3 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

hours, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

minutes, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

seconds, err := strconv.ParseFloat(fields[2], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

return 0, 0, 0, hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) + seconds*float64(gotime.Second), nil
}

// extractHourMinute extracts hour and minute from a string and its format is `HH:MM`.
func extractHourMinute(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, ":")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

hours, err := strconv.ParseFloat(fields[0], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

minutes, err := strconv.ParseFloat(fields[1], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

return 0, 0, 0, hours*float64(gotime.Hour) + minutes*float64(gotime.Minute), nil
}

// extractDayMicrosecond extracts day and microsecond from a string and its format is `DD HH:MM:SS.FFFFFF`.
func extractDayMicrosecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, " ")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

days, err := strconv.ParseInt(fields[0], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
// extractTimeValue extracts years, months, days, microseconds from a string
// MySQL permits any punctuation delimiter in the expr format.
// See https://dev.mysql.com/doc/refman/8.0/en/expressions.html#temporal-intervals
func extractTimeValue(format string, index, cnt int) (int64, int64, int64, float64, error) {
neg := false
originalFmt := format
format = strings.TrimSpace(format)
if len(format) > 0 && format[0] == '-' {
neg = true
format = format[1:]
}

_, _, _, value, err := extractHourMicrosecond(fields[1])
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
fields := make([]string, TimeValueCnt)
for i := range fields {
fields[i] = "0"
}

return 0, 0, days, value, nil
}

// extractDaySecond extracts day and hour from a string and its format is `DD HH:MM:SS`.
func extractDaySecond(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, " ")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
matches := numericRegex.FindAllString(format, -1)
if len(matches) > cnt {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

days, err := strconv.ParseInt(fields[0], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
for i := range matches {
if neg {
fields[index] = "-" + matches[len(matches)-1-i]
} else {
fields[index] = matches[len(matches)-1-i]
}
index--
}

_, _, _, value, err := extractHourSecond(fields[1])
years, err := strconv.ParseInt(fields[YearIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

return 0, 0, days, value, nil
}

// extractDayMinute extracts day and minute from a string and its format is `DD HH:MM`.
func extractDayMinute(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, " ")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

days, err := strconv.ParseInt(fields[0], 10, 64)
months, err := strconv.ParseInt(fields[MonthIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

_, _, _, value, err := extractHourMinute(fields[1])
days, err := strconv.ParseInt(fields[DayIndex], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

return 0, 0, days, value, nil
}

// extractDayHour extracts day and hour from a string and its format is `DD HH`.
func extractDayHour(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, " ")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

days, err := strconv.ParseInt(fields[0], 10, 64)
hours, err := strconv.ParseFloat(fields[HourIndex], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

hours, err := strconv.ParseFloat(fields[1], 64)
minutes, err := strconv.ParseFloat(fields[MinuteIndex], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

return 0, 0, days, hours * float64(gotime.Hour), nil
}

// extractYearMonth extracts year and month from a string and its format is `YYYY-MM`.
func extractYearMonth(format string) (int64, int64, int64, float64, error) {
fields := strings.Split(format, "-")
if len(fields) != 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
}

years, err := strconv.ParseInt(fields[0], 10, 64)
seconds, err := strconv.ParseFloat(fields[SecondIndex], 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}

months, err := strconv.ParseInt(fields[1], 10, 64)
microseconds, err := strconv.ParseFloat(alignFrac(fields[MicrosecondIndex], MaxFsp), 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(originalFmt)
}
durations := hours*float64(gotime.Hour) + minutes*float64(gotime.Minute) +
seconds*float64(gotime.Second) + microseconds*float64(gotime.Microsecond)

return years, months, 0, 0, nil
return years, months, days, durations, nil
}

// ExtractTimeValue extracts time value from time unit and format.
Expand All @@ -1791,27 +1668,27 @@ func ExtractTimeValue(unit string, format string) (int64, int64, int64, float64,
case "MICROSECOND", "SECOND", "MINUTE", "HOUR", "DAY", "WEEK", "MONTH", "QUARTER", "YEAR":
return extractSingleTimeValue(unit, format)
case "SECOND_MICROSECOND":
return extractSecondMicrosecond(format)
return extractTimeValue(format, MicrosecondIndex, SecondMicrosecondMaxCnt)
case "MINUTE_MICROSECOND":
return extractMinuteMicrosecond(format)
return extractTimeValue(format, MicrosecondIndex, MinuteMicrosecondMaxCnt)
case "MINUTE_SECOND":
return extractMinuteSecond(format)
return extractTimeValue(format, SecondIndex, MinuteSecondMaxCnt)
case "HOUR_MICROSECOND":
return extractHourMicrosecond(format)
return extractTimeValue(format, MicrosecondIndex, HourMicrosecondMaxCnt)
case "HOUR_SECOND":
return extractHourSecond(format)
return extractTimeValue(format, SecondIndex, HourSecondMaxCnt)
case "HOUR_MINUTE":
return extractHourMinute(format)
return extractTimeValue(format, MinuteIndex, HourMinuteMaxCnt)
case "DAY_MICROSECOND":
return extractDayMicrosecond(format)
return extractTimeValue(format, MicrosecondIndex, DayMicrosecondMaxCnt)
case "DAY_SECOND":
return extractDaySecond(format)
return extractTimeValue(format, SecondIndex, DaySecondMaxCnt)
case "DAY_MINUTE":
return extractDayMinute(format)
return extractTimeValue(format, MinuteIndex, DayMinuteMaxCnt)
case "DAY_HOUR":
return extractDayHour(format)
return extractTimeValue(format, HourIndex, DayHourMaxCnt)
case "YEAR_MONTH":
return extractYearMonth(format)
return extractTimeValue(format, MonthIndex, YearMonthMaxCnt)
default:
return 0, 0, 0, 0, errors.Errorf("invalid singel timeunit - %s", unit)
}
Expand Down Expand Up @@ -2378,6 +2255,9 @@ var twoDigitRegex = regexp.MustCompile("^[1-9][0-9]?")
// oneToSixDigitRegex: it was just for [0, 999999]
var oneToSixDigitRegex = regexp.MustCompile("^[0-9]{0,6}")

// numericRegex: it was for any numeric characters
var numericRegex = regexp.MustCompile("[0-9]+")

// parseTwoNumeric is used for pattens 0..31 0..24 0..60 and so on.
// It returns the parsed int, and remain data after parse.
func parseTwoNumeric(input string) (int, string) {
Expand Down
Loading