FB-1895: Implement DateTimeFromParts (#2296)

This commit is contained in:
rachithrr 2023-03-07 16:47:07 -06:00 committed by GitHub
parent dc6cbad3fc
commit 909c62d44e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 155 additions and 3 deletions

View file

@ -138,6 +138,7 @@ const (
// time quantum function eval
ErrQRangeFromAndToTimeCannotBeBothNull errors.Code = "ErrQRangeFromAndToTimeCannotBeBothNull"
ErrQRangeInvalidUse errors.Code = "ErrQRangeInvalidUse"
ErrYearOutOfRange errors.Code = "ErrYearOutOfRange"
)
func NewErrDuplicateColumn(line int, col int, column string) error {
@ -852,3 +853,10 @@ func NewErrQRangeInvalidUse(line, col int) error {
fmt.Sprintf("[%d:%d] calling ranqeq() usage invalid", line, col),
)
}
func NewErrYearOutOfRange(line, col int, year int) error {
return errors.New(
ErrYearOutOfRange,
fmt.Sprintf("[%d:%d] year '%d' out of range [0,9999]", line, col, year),
)
}

View file

@ -1580,6 +1580,8 @@ func (n *callPlanExpression) Evaluate(currentRow []interface{}) (interface{}, er
// time quantum functions
case "RANGEQ":
return n.EvaluateRangeQ(currentRow)
case "DATETIMEFROMPARTS":
return n.EvaluateDateTimeFromParts(currentRow)
case "DATETIMEADD":
return n.EvaluateDatetimeAdd(currentRow)
default:

View file

@ -261,6 +261,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(ctx context.Context, call *pars
// time quantum funtions
case "RANGEQ":
return p.analyzeFunctionRangeQ(call, scope)
case "DATETIMEFROMPARTS":
return p.analyzeFunctionDateTimeFromParts(call, scope)
case "DATETIMEADD":
return p.analyzeFunctionDatetimeAdd(call, scope)

View file

@ -101,6 +101,24 @@ func (p *ExecutionPlanner) analyzeFunctionDatetimeAdd(call *parser.Call, scope p
// 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
}
@ -123,7 +141,6 @@ func (p *ExecutionPlanner) analyzeFunctionDateTimeName(call *parser.Call, scope
//return int
call.ResultDataType = parser.NewDataTypeString()
return call, nil
}
@ -209,6 +226,34 @@ func (n *callPlanExpression) EvaluateDatepart(currentRow []interface{}) (interfa
}
// 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)
}
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.NewErrYearOutOfRange(0, 0, dt.Year())
}
return dt, nil
}
func (n *callPlanExpression) EvaluateToTimestamp(currentRow []interface{}) (interface{}, error) {
// retrieve param1, the number to be converted to timestamp
param1, err := n.args[0].Evaluate(currentRow)

View file

@ -1,6 +1,9 @@
package defs
import "time"
import (
"fmt"
"time"
)
// datepart tests
var datePartTests = TableTest{
@ -19,24 +22,56 @@ var datePartTests = TableTest{
),
SQLTests: []SQLTest{
{
name: "DatePartIncorrectParamsCount",
SQLs: sqls(
"select datepart()",
),
ExpErr: "count of formal parameters (2) does not match count of actual parameters (0)",
},
{
name: "DatePartIntError",
SQLs: sqls(
"select datepart(1, 2)",
),
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'string'",
},
{
name: "DatePartInvalidParam",
SQLs: sqls(
"select datepart('1', current_timestamp)",
),
ExpErr: "invalid value '1' for parameter 'interval'",
},
{
name: "ToTimestampWrongParamsCount",
SQLs: sqls(
"select totimestamp()",
),
ExpErr: "count of formal parameters (2) does not match count of actual parameters (0)",
},
{
name: "ToTimestampStringError",
SQLs: sqls(
"select totimestamp('a')",
),
ExpErr: "an expression of type 'string' cannot be passed to a parameter of type 'int'",
},
{
name: "ToTimestampIntError",
SQLs: sqls(
"select totimestamp(1, 2)",
),
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'string'",
},
{
name: "ToTimestampInvalid",
SQLs: sqls(
"select totimestamp(1, 'x')",
),
ExpErr: "invalid value 'x' for parameter 'timeunit'",
},
{
name: "DATEPARTYY",
SQLs: sqls(
"select _id, datepart('yy', ts) from dateparttests",
),
@ -50,6 +85,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DATEPARTYD",
SQLs: sqls(
"select _id, datepart('yd', ts) from dateparttests",
),
@ -63,6 +99,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DATEPARTM",
SQLs: sqls(
"select _id, datepart('m', ts) from dateparttests",
),
@ -76,6 +113,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DATEPARTD",
SQLs: sqls(
"select _id, datepart('d', ts) from dateparttests",
),
@ -89,6 +127,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DATEPARTW",
SQLs: sqls(
"select _id, datepart('w', ts) from dateparttests",
),
@ -102,6 +141,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DATEPARTWK",
SQLs: sqls(
"select _id, datepart('wk', ts) from dateparttests",
),
@ -115,6 +155,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DATEPARTHH",
SQLs: sqls(
"select _id, datepart('hh', ts) from dateparttests",
),
@ -128,6 +169,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DatePartMI",
SQLs: sqls(
"select _id, datepart('mi', ts) from dateparttests",
),
@ -141,6 +183,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DatePartS",
SQLs: sqls(
"select _id, datepart('s', ts) from dateparttests",
),
@ -154,6 +197,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DatePartMS",
SQLs: sqls(
"select _id, datepart('ms', ts) from dateparttests",
),
@ -180,6 +224,7 @@ var datePartTests = TableTest{
Compare: CompareExactUnordered,
},
{
name: "DatePartNS",
SQLs: sqls(
"select _id, datepart('ns', ts) from dateparttests",
),
@ -194,6 +239,7 @@ var datePartTests = TableTest{
},
{
//test datepart(timestamp, part) for implicit conversion of integer value passed as argument to timestamp param
name: "DatePartImplicitIntConversion",
SQLs: sqls(
"select datepart('yy', 0) as \"yy\", datepart('m', 0) as \"m\", datepart('d', 0) as \"d\"",
),
@ -244,7 +290,7 @@ var toTimestampTests = TableTest{
ExpErr: "invalid value 'x' for parameter 'timeunit'",
},
{
//test ToTimestamp(num, timeunit) for all possible time unit values
name: "ToTimestampAllPossibleValues",
SQLs: sqls(
"select totimestamp(1000) as \"default\", totimestamp(1000, 's') as \"s\", totimestamp(1000000, 'ms') as \"ms\", totimestamp(1000000000, 'us') as \"us\", totimestamp(1000000000, 'µs') as \"µs\", totimestamp(1000000000000, 'ns') as \"ns\"",
),
@ -266,6 +312,55 @@ var toTimestampTests = TableTest{
),
Compare: CompareExactUnordered,
},
{
name: "DateTimeFromPartsParamsCountMismatch",
SQLs: sqls(
"select datetimefromparts(12,32,43,34,34,34)",
),
ExpErr: "count of formal parameters (7) does not match count of actual parameters (6)",
},
{
name: "DateTimeFromPartsParamsTypeMismatch",
SQLs: sqls(
"select datetimefromparts(12,32,43,34,34,34,'foo')",
),
ExpErr: "an expression of type 'string' cannot be passed to a parameter of type 'int'",
},
{
name: "DateTimeFromPartsYearOutOfRange",
SQLs: sqls(
"select datetimefromparts(10000,1,1,1,1,1,1)",
),
ExpErr: "[0:0] year '10000' out of range [0,9999]",
},
{
name: "DateTimeFromPartsKnownTimestamp",
SQLs: sqls(
fmt.Sprintf("select datetimefromparts(%d,%d,%d,%d,%d,%d,%d) as datetime", knownTimestamp().Year(),
knownTimestamp().Month(), knownTimestamp().Day(), knownTimestamp().Hour(), knownTimestamp().Minute(),
knownTimestamp().Second(), knownTimestamp().Nanosecond()/(1000*1000)),
),
ExpHdrs: hdrs(
hdr("datetime", fldTypeTimestamp),
),
ExpRows: rows(
row(knownTimestamp()),
),
Compare: CompareExactUnordered,
},
{
name: "DateTimeFromPartsAllZeros",
SQLs: sqls(
fmt.Sprintf("select datetimefromparts(%d,%d,%d,%d,%d,%d,%d) as datetime", 0, 1, 1, 0, 0, 0, 0),
),
ExpHdrs: hdrs(
hdr("datetime", fldTypeTimestamp),
),
ExpRows: rows(
row(time.Date(0, 1, 1, 0, 0, 0, 0, time.UTC)),
),
Compare: CompareExactUnordered,
},
},
}