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

Merged
merged 11 commits into from
Mar 30, 2019
82 changes: 63 additions & 19 deletions types/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -1753,41 +1753,82 @@ func extractDayMinute(format string) (int64, int64, int64, float64, error) {
return 0, 0, days, value, nil
}

// extractDayHour extracts day and hour from a string and its format is `DD HH`.
// extractDayHour extracts day and hour from a string and its suggested format is `DD HH`.
// MySQL permits any punctuation delimiter in the expr format. it may start with a - for negative intervals
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)
if len(format) == 0 {
return 0, 0, 0, 0, nil
}
neg := false
format0 := format
if format[0] == '-' {
neg = true
format = format[1:]
}
fields := numericRegex.FindAllString(format, -1)

days, err := strconv.ParseInt(fields[0], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
if len(fields) == 0 {
return 0, 0, 0, 0, nil
} else if len(fields) > 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format0)
}
field0 := "0"
if len(fields) > 1 {
field0 = fields[len(fields)-2]
}
field1 := fields[len(fields)-1]

hours, err := strconv.ParseFloat(fields[1], 64)
days, err := strconv.ParseInt(field0, 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format0)
}
hours, err := strconv.ParseFloat(field1, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format0)
}
if neg {
days *= -1
hours *= -1
}

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

// extractYearMonth extracts year and month from a string and its format is `YYYY-MM`.
// extractYearMonth extracts year and month from a string and its suggested format is `YYYY-MM`.
// MySQL permits any punctuation delimiter in the expr format. it may start with a - for negative intervals
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)
if len(format) == 0 {
return 0, 0, 0, 0, nil
}
neg := false
format0 := format
if format[0] == '-' {
neg = true
format = format[1:]
}
fields := numericRegex.FindAllString(format, -1)

years, err := strconv.ParseInt(fields[0], 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
if len(fields) == 0 {
return 0, 0, 0, 0, nil
} else if len(fields) > 2 {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format0)
}

months, err := strconv.ParseInt(fields[1], 10, 64)
field0 := "0"
if len(fields) > 1 {
field0 = fields[len(fields)-2]
}
field1 := fields[len(fields)-1]
years, err := strconv.ParseInt(field0, 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format)
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format0)
}
months, err := strconv.ParseInt(field1, 10, 64)
if err != nil {
return 0, 0, 0, 0, ErrIncorrectDatetimeValue.GenWithStackByArgs(format0)
}
if neg {
years *= -1
months *= -1
}

return years, months, 0, 0, nil
Expand Down Expand Up @@ -2379,6 +2420,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
8 changes: 8 additions & 0 deletions types/time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1209,7 +1209,15 @@ func (s *testTimeSuite) TestExtractTimeValue(c *C) {
{"12 14:00:00", "DAY_SECOND", 0, 0, 12, 5.04e+13},
{"12 14:00", "DAY_MINUTE", 0, 0, 12, 5.04e+13},
{"12 14", "DAY_HOUR", 0, 0, 12, 5.04e+13},
{"1:1", "DAY_HOUR", 0, 0, 1, 3.6e+12},
{"aa1bb1", "DAY_HOUR", 0, 0, 1, 3.6e+12},
{"-1:1", "DAY_HOUR", 0, 0, -1, -3.6e+12},
{"-aa1bb1", "DAY_HOUR", 0, 0, -1, -3.6e+12},
{"2019-12", "YEAR_MONTH", 2019, 12, 0, 0},
{"1 1", "YEAR_MONTH", 1, 1, 0, 0},
{"aa1bb1", "YEAR_MONTH", 1, 1, 0, 0},
{"-1 1", "YEAR_MONTH", -1, -1, 0, 0},
{"-aa1bb1", "YEAR_MONTH", -1, -1, 0, 0},
}
for _, col := range tbl {
res1, res2, res3, res4, err := types.ExtractTimeValue(col.unit, col.format)
Expand Down