package planner import ( "fmt" "strings" "time" featurebase "github.com/featurebasedb/featurebase/v3" "github.com/featurebasedb/featurebase/v3/sql3" "github.com/featurebasedb/featurebase/v3/sql3/parser" ) const intervalYear = "YY" const intervalYearDay = "YD" const intervalMonth = "M" const intervalDay = "D" const intervalWeeKDay = "W" const intervalWeek = "WK" const intervalHour = "HH" const intervalMinute = "MI" const intervalSecond = "S" const intervalMillisecond = "MS" const intervalMicrosecond = "US" const intervalNanosecond = "NS" func (p *ExecutionPlanner) analyzeFunctionDateTimePart(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 2 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args)) } // interval intervalType := parser.NewDataTypeString() if !typesAreAssignmentCompatible(intervalType, call.Args[0].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), intervalType.TypeDescription()) } // date dateType := parser.NewDataTypeTimestamp() if !typesAreAssignmentCompatible(dateType, call.Args[1].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeDescription(), dateType.TypeDescription()) } // return int call.ResultDataType = parser.NewDataTypeInt() return call, nil } func (p *ExecutionPlanner) analyzeFunctionToTimestamp(call *parser.Call, scope parser.Statement) (parser.Expr, error) { // param1 is the number to be converted to timestamp. This param is required. // param2 is the time unit of the numeric value in param 1. This param is optional. // ToTimestamp can be invoked with just param1. if len(call.Args) != 1 && len(call.Args) != 2 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args)) } // param1 is a integer of type int64 param1Type := parser.NewDataTypeInt() if !typesAreAssignmentCompatible(param1Type, call.Args[0].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), param1Type.TypeDescription()) } // param2 is a string and it should be one of 's', 'ms', 'us', 'ns'. // param2 is optional, will be defaulted to 's' if not supplied. if len(call.Args) == 2 { param2Type := parser.NewDataTypeString() if !typesAreAssignmentCompatible(param2Type, call.Args[1].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeDescription(), param2Type.TypeDescription()) } } // ToTimestamp returns a timestamp calculated from param1 using time unit passed in param 2 call.ResultDataType = parser.NewDataTypeTimestamp() return call, nil } func (p *ExecutionPlanner) analyzeFunctionDatetimeAdd(call *parser.Call, scope parser.Statement) (parser.Expr, error) { // param1 is the time unit of duration to be added to the target timestamp. // param2 is the time duration to be added to the target timestamp. // param3 is the target timestamp to which the time duration to be added. if len(call.Args) != 3 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 3, len(call.Args)) } // param1- time unit is a string and it should be one of 'yy','m','d','hh','mi','s', 'ms', 'us', 'ns'. param1Type := parser.NewDataTypeString() if !typesAreAssignmentCompatible(param1Type, call.Args[0].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), param1Type.TypeDescription()) } // param2- time duration is a int param2Type := parser.NewDataTypeInt() if !typesAreAssignmentCompatible(param2Type, call.Args[1].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeDescription(), param2Type.TypeDescription()) } // param3- target datetime to which the duration to be added to param3Type := parser.NewDataTypeTimestamp() if !typesAreAssignmentCompatible(param3Type, call.Args[2].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[2].Pos().Line, call.Args[2].Pos().Column, call.Args[2].DataType().TypeDescription(), param3Type.TypeDescription()) } // DatetimeAdd returns a timestamp calculated by adding param2 to param3 using time unit passed in param 1 call.ResultDataType = parser.NewDataTypeTimestamp() return call, nil } func (p *ExecutionPlanner) analyzeFunctionDateTimeFromParts(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 7 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 7, len(call.Args)) } intType := parser.NewDataTypeInt() for _, part := range call.Args { if !typesAreAssignmentCompatible(intType, part.DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(part.Pos().Line, part.Pos().Column, part.DataType().TypeDescription(), intType.TypeDescription()) } } call.ResultDataType = parser.NewDataTypeTimestamp() return call, nil } func (p *ExecutionPlanner) analyzeFunctionDateTimeName(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 2 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args)) } // interval intervalType := parser.NewDataTypeString() if !typesAreAssignmentCompatible(intervalType, call.Args[0].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), intervalType.TypeDescription()) } // date dateType := parser.NewDataTypeTimestamp() if !typesAreAssignmentCompatible(dateType, call.Args[1].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeDescription(), dateType.TypeDescription()) } //return int call.ResultDataType = parser.NewDataTypeString() return call, nil } func (p *ExecutionPlanner) analyzeFunctionDateTrunc(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 2 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 2, len(call.Args)) } // interval intervalType := parser.NewDataTypeString() if !typesAreAssignmentCompatible(intervalType, call.Args[0].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), intervalType.TypeDescription()) } // date dateType := parser.NewDataTypeTimestamp() if !typesAreAssignmentCompatible(dateType, call.Args[1].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeDescription(), dateType.TypeDescription()) } //return int call.ResultDataType = parser.NewDataTypeString() return call, nil } // analyzeFunctionDateTimeDiff ensures a timeunit and start and end timestamps. func (p *ExecutionPlanner) analyzeFunctionDateTimeDiff(call *parser.Call, scope parser.Statement) (parser.Expr, error) { if len(call.Args) != 3 { return nil, sql3.NewErrCallParameterCountMismatch(call.Rparen.Line, call.Rparen.Column, call.Name.Name, 3, len(call.Args)) } // interval intervalType := parser.NewDataTypeString() if !typesAreAssignmentCompatible(intervalType, call.Args[0].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[0].Pos().Line, call.Args[0].Pos().Column, call.Args[0].DataType().TypeDescription(), intervalType.TypeDescription()) } dateType := parser.NewDataTypeTimestamp() if !typesAreAssignmentCompatible(dateType, call.Args[1].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[1].Pos().Line, call.Args[1].Pos().Column, call.Args[1].DataType().TypeDescription(), dateType.TypeDescription()) } if !typesAreAssignmentCompatible(dateType, call.Args[2].DataType()) { return nil, sql3.NewErrParameterTypeMistmatch(call.Args[2].Pos().Line, call.Args[2].Pos().Column, call.Args[2].DataType().TypeDescription(), dateType.TypeDescription()) } call.ResultDataType = parser.NewDataTypeInt() return call, nil } func (n *callPlanExpression) EvaluateDateTimePart(currentRow []interface{}) (interface{}, error) { intervalEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } dateEval, err := n.args[1].Evaluate(currentRow) if err != nil { return nil, err } // nil if anything is nil if intervalEval == nil || dateEval == nil { return nil, nil } // get the date value coercedDate, err := coerceValue(n.args[1].Type(), parser.NewDataTypeTimestamp(), dateEval, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } date, dateOk := coercedDate.(time.Time) if !dateOk { return nil, sql3.NewErrInternalf("unable to convert value") } // get the interval value coercedInterval, err := coerceValue(n.args[0].Type(), parser.NewDataTypeString(), intervalEval, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } interval, intervalOk := coercedInterval.(string) if !intervalOk { return nil, sql3.NewErrInternalf("unable to convert value") } switch strings.ToUpper(interval) { case intervalYear: return int64(date.Year()), nil case intervalYearDay: return int64(date.YearDay()), nil case intervalMonth: return int64(date.Month()), nil case intervalDay: return int64(date.Day()), nil case intervalWeeKDay: return int64(date.Weekday()), nil case intervalWeek: _, isoWeek := date.ISOWeek() return int64(isoWeek), nil case intervalHour: return int64(date.Hour()), nil case intervalMinute: return int64(date.Minute()), nil case intervalSecond: return int64(date.Second()), nil case intervalMillisecond: return int64(date.Nanosecond() / 1000000), nil case intervalMicrosecond: return int64(date.Nanosecond() / 1000), nil case intervalNanosecond: return int64(date.Nanosecond()), nil default: return nil, sql3.NewErrCallParameterValueInvalid(0, 0, interval, "interval") } } // EvaluateDateTimeFromParts evaluates the call to date_time_from_parts. This uses the base time.Date() function. func (n *callPlanExpression) EvaluateDateTimeFromParts(currentRow []interface{}) (interface{}, error) { timestamps := make([]int, len(n.args)) for i, arg := range n.args { param, err := arg.Evaluate(currentRow) if err != nil { return nil, err } else if param == nil { return nil, nil } coercedValue, err := coerceValue(arg.Type(), parser.NewDataTypeInt(), param, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } val, ok := coercedValue.(int64) if !ok { return nil, sql3.NewErrInternalf("unable to convert value") } timestamps[i] = int(val) } if val, ok := isValidDateTimeParts(timestamps); !ok { return nil, sql3.NewErrInvalidDatetimePart(0, 0, val) } dt := time.Date(timestamps[0], time.Month(timestamps[1]), timestamps[2], timestamps[3], timestamps[4], timestamps[5], timestamps[6]*1000*1000, time.UTC) if dt.Year() < 0 || dt.Year() > 9999 { return nil, sql3.NewErrInvalidDatetimePart(0, 0, dt.Year()) } return dt, nil } // isValidDateTimeParts returns true if the year is between 0 and 9999 and the date and time exists(think of leap year). // the argument is a slice of ints representing the year, month, day, hour, minutes, seconds, and milliseconds. // If any value in the slice falls outside its range, the value and false are returned. func isValidDateTimeParts(timestamps []int) (value int, ok bool) { if timestamps[0] < 0 || timestamps[0] > 9999 { return timestamps[0], false } if timestamps[1] < 1 || timestamps[1] > 12 { return timestamps[1], false } switch timestamps[1] { case 1, 3, 5, 7, 8, 10, 12: if timestamps[2] < 1 || timestamps[2] > 31 { return timestamps[2], false } case 4, 6, 9, 11: if timestamps[2] < 1 || timestamps[2] > 30 { return timestamps[2], false } case 2: if timestamps[2] < 1 || timestamps[2] > 29 { return timestamps[2], false } if !(timestamps[0]%4 == 0 && timestamps[0]%100 != 0 || timestamps[0]%400 == 0) { if timestamps[2] == 29 { return timestamps[2], false } } } if timestamps[3] < 0 || timestamps[3] > 23 { return timestamps[3], false } if timestamps[4] < 0 || timestamps[4] > 59 { return timestamps[4], false } if timestamps[5] < 0 || timestamps[5] > 59 { return timestamps[5], false } if timestamps[6] < 0 || timestamps[6] > 999 { return timestamps[6], false } return 0, true } func (n *callPlanExpression) EvaluateToTimestamp(currentRow []interface{}) (interface{}, error) { // retrieve param1, the number to be converted to timestamp param1, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } else if param1 == nil { // if the param1 is null silently return null timestamp value return nil, nil } coercedParam1, err := coerceValue(n.args[0].Type(), parser.NewDataTypeInt(), param1, parser.Pos{Line: 0, Column: 0}) if err != nil { // raise error if param 1 is not an integer. Should we return nil instead of raising error here? see note at return. return nil, err } num, ok := coercedParam1.(int64) if !ok { // raise error if param 1 is not an integer. Should we return nil instead of raising error here? see note at return. return nil, sql3.NewErrInternalf("unable to convert value") } // retrieve param2, time unit for param1, if not supplied default to seconds 's'. var unit string = featurebase.TimeUnitSeconds if len(n.args) == 2 { param2, err := n.args[1].Evaluate(currentRow) if err != nil { // raise error if unable to retieve the argument for param2 return nil, err } coercedParam2, err := coerceValue(n.args[1].Type(), parser.NewDataTypeString(), param2, parser.Pos{Line: 0, Column: 0}) if err != nil { // raise error if param2 is not a string return nil, err } unit, ok = coercedParam2.(string) if !ok { // raise error if param2 is not a string return nil, sql3.NewErrInternalf("unable to convert value") } if !featurebase.IsValidTimeUnit(unit) { // raise error is param2 is not a valid time unit return nil, sql3.NewErrCallParameterValueInvalid(0, 0, unit, "timeunit") } } // should we throw error or return nil if the conversion fails? what is the desired behaviour when ToTimestamp errors for one bad record in a batch of thousands? return featurebase.ValToTimestamp(unit, num) } func (n *callPlanExpression) EvaluateDateTimeName(currentRow []interface{}) (interface{}, error) { intervalEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } dateEval, err := n.args[1].Evaluate(currentRow) if err != nil { return nil, err } // nil if anything is nil if intervalEval == nil || dateEval == nil { return nil, nil } //get the date value coercedDate, err := coerceValue(n.args[1].Type(), parser.NewDataTypeTimestamp(), dateEval, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } date, dateOk := coercedDate.(time.Time) if !dateOk { return nil, sql3.NewErrInternalf("unable to convert value") } //get the interval value coercedInterval, err := coerceValue(n.args[0].Type(), parser.NewDataTypeString(), intervalEval, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } interval, intervalOk := coercedInterval.(string) if !intervalOk { return nil, sql3.NewErrInternalf("unable to convert value") } switch strings.ToUpper(interval) { case intervalYear: return fmt.Sprint(date.Year()), nil case intervalYearDay: return fmt.Sprint(date.YearDay()), nil case intervalMonth: return fmt.Sprint(date.Month()), nil case intervalDay: return fmt.Sprint(date.Day()), nil case intervalWeeKDay: return fmt.Sprint(date.Weekday()), nil case intervalWeek: _, isoWeek := date.ISOWeek() return fmt.Sprint(isoWeek), nil case intervalHour: return fmt.Sprint(date.Hour()), nil case intervalMinute: return fmt.Sprint(date.Minute()), nil case intervalSecond: return fmt.Sprint(date.Second()), nil case intervalMillisecond: return fmt.Sprint(date.Nanosecond() / 1000000), nil case intervalMicrosecond: return fmt.Sprint(date.Nanosecond() / 1000), nil case intervalNanosecond: return fmt.Sprint(date.Nanosecond()), nil default: return nil, sql3.NewErrCallParameterValueInvalid(0, 0, interval, "interval") } } func (n *callPlanExpression) EvaluateDatetimeAdd(currentRow []interface{}) (interface{}, error) { // retrieve param1, timeunit of the value to be added to the target timestamp param1, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } coercedParam1, err := coerceValue(n.args[0].Type(), parser.NewDataTypeString(), param1, parser.Pos{Line: 0, Column: 0}) if err != nil { // raise error if param 1 is not string. return nil, err } timeunit, ok := coercedParam1.(string) if !ok { // raise error if param 1 is not string. return nil, sql3.NewErrInternalf("unable to convert value") } // retrieve param2, timeduration to be added to the target timestamp. param2, err := n.args[1].Evaluate(currentRow) if err != nil { // raise error if unable to retieve the argument for param2 return nil, err } // retrieve param3, target timestamp to which the timeduration to be added to. param3, err := n.args[2].Evaluate(currentRow) if err != nil { // raise error if unable to retieve the argument for param3 return nil, err } if param2 == nil || param3 == nil { // if either of timeduration or target datetime is null then return null return nil, nil } coercedParam2, err := coerceValue(n.args[1].Type(), parser.NewDataTypeInt(), param2, parser.Pos{Line: 0, Column: 0}) if err != nil { // raise error if param2 is not a string return nil, err } timeduration, ok := coercedParam2.(int64) if !ok { // raise error if param2 is not a integer return nil, sql3.NewErrInternalf("unable to convert value") } coercedParam3, err := coerceValue(n.args[2].Type(), parser.NewDataTypeTimestamp(), param3, parser.Pos{Line: 0, Column: 0}) if err != nil { // raise error if param3 is not a timestamp return nil, err } target, ok := coercedParam3.(time.Time) if !ok { // raise error if param3 is not a datetime return nil, sql3.NewErrInternalf("unable to convert value") } if !isValidTimeInterval(strings.ToUpper(timeunit)) { // raise error if timeunit value is invalid return nil, sql3.NewErrCallParameterValueInvalid(0, 0, timeunit, "timeunit") } else if target.IsZero() { // return nil if target is nil return nil, nil } else if timeduration == 0 { // return target if duration to add is 0 return target, nil } switch strings.ToUpper(timeunit) { case intervalYear: return target.AddDate(int(timeduration), 0, 0), nil case intervalMonth: return target.AddDate(0, int(timeduration), 0), nil case intervalDay: return target.AddDate(0, 0, int(timeduration)), nil case intervalHour: return target.Add(time.Hour * time.Duration(timeduration)), nil case intervalMinute: return target.Add(time.Minute * time.Duration(timeduration)), nil case intervalSecond: return target.Add(time.Second * time.Duration(timeduration)), nil case intervalMillisecond: return target.Add(time.Millisecond * time.Duration(timeduration)), nil case intervalMicrosecond: return target.Add(time.Microsecond * time.Duration(timeduration)), nil case intervalNanosecond: return target.Add(time.Nanosecond * time.Duration(timeduration)), nil default: return nil, sql3.NewErrCallParameterValueInvalid(0, 0, timeunit, "timeunit") } } func (n *callPlanExpression) EvaluateDateTrunc(currentRow []interface{}) (interface{}, error) { intervalEval, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } dateEval, err := n.args[1].Evaluate(currentRow) if err != nil { return nil, err } // nil if anything is nil if intervalEval == nil || dateEval == nil { return nil, nil } //get the date value coercedDate, err := coerceValue(n.args[1].Type(), parser.NewDataTypeTimestamp(), dateEval, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } date, dateOk := coercedDate.(time.Time) if !dateOk { return nil, sql3.NewErrInternalf("unable to convert value") } //get the interval value coercedInterval, err := coerceValue(n.args[0].Type(), parser.NewDataTypeString(), intervalEval, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } interval, intervalOk := coercedInterval.(string) if !intervalOk { return nil, sql3.NewErrInternalf("unable to convert value") } switch strings.ToUpper(interval) { case intervalYear: return date.Format("2006"), nil case intervalMonth: return date.Format("2006-01"), nil case intervalDay: return date.Format("2006-01-02"), nil case intervalHour: return date.Format("2006-01-02T15"), nil case intervalMinute: return date.Format("2006-01-02T15:04"), nil case intervalSecond: return date.Format("2006-01-02T15:04:05"), nil case intervalMillisecond: return date.Format("2006-01-02T15:04:05.000"), nil case intervalMicrosecond: return date.Format("2006-01-02T15:04:05.000000"), nil case intervalNanosecond: return date.Format("2006-01-02T15:04:05.000000000"), nil default: return nil, sql3.NewErrCallParameterValueInvalid(0, 0, interval, "interval") } } // isValidTimeInterval returns true if part is valid. func isValidTimeInterval(unit string) bool { switch unit { case intervalYear, intervalYearDay, intervalMonth, intervalDay, intervalWeeKDay, intervalWeek, intervalHour, intervalMinute, intervalSecond, intervalMillisecond, intervalMicrosecond, intervalNanosecond: return true default: return false } } // EvaluateDatetimeDiff takes three arguments: // 1. param1, timeunit of the value to be subtracted from the target timestamp // 2. param2, starttime to be subtracted from the endtime timestamp // 3. param3, endtime timestamp to which the starttime to be subtracted from. // It returns the difference between the two timestamps. func (n *callPlanExpression) EvaluateDatetimeDiff(currentRow []interface{}) (interface{}, error) { param1, err := n.args[0].Evaluate(currentRow) if err != nil { return nil, err } if param1 == nil { return nil, nil } cp, err := coerceValue(n.args[0].Type(), parser.NewDataTypeString(), param1, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } timeunit, ok := cp.(string) if !ok { return nil, sql3.NewErrInternalf("unable to convert value") } param2, err := n.args[1].Evaluate(currentRow) if err != nil { return nil, err } coercedParam, err := coerceValue(n.args[1].Type(), parser.NewDataTypeTimestamp(), param2, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } if coercedParam == nil { return nil, nil } startDate, ok := coercedParam.(time.Time) if !ok { return nil, sql3.NewErrInternalf("unable to convert value") } if coercedParam == nil { return nil, nil } param3, err := n.args[2].Evaluate(currentRow) if err != nil { return nil, err } if param3 == nil { return nil, nil } coercedParam, err = coerceValue(n.args[2].Type(), parser.NewDataTypeTimestamp(), param3, parser.Pos{Line: 0, Column: 0}) if err != nil { return nil, err } endDate, ok := coercedParam.(time.Time) if !ok { return nil, sql3.NewErrInternalf("unable to convert value") } var diff int64 switch strings.ToUpper(timeunit) { case intervalYear: diff = int64(endDate.Year() - startDate.Year()) case intervalMonth: diff = int64((endDate.Year()-startDate.Year())*12 + int(endDate.Month()-startDate.Month())) case intervalDay: diff = int64(endDate.Sub(startDate).Hours() / 24) case intervalHour: diff = int64(endDate.Sub(startDate).Hours()) case intervalMinute: diff = int64(endDate.Sub(startDate).Minutes()) case intervalSecond: diff = int64(endDate.Sub(startDate).Seconds()) case intervalMillisecond: diff = endDate.Sub(startDate).Milliseconds() case intervalMicrosecond: diff = endDate.Sub(startDate).Microseconds() case intervalNanosecond: diff = endDate.Sub(startDate).Nanoseconds() default: return nil, sql3.NewErrCallParameterValueInvalid(0, 0, timeunit, "timeunit") } if diff == (-1<<63) || diff == ((1<<63)-1) { return nil, sql3.NewErrOutputValueOutOfRange(0, 0) } return diff, nil }