Added date_trunc time/date scalar function (#2312)

FB-1961
Added function and test coverage.
This commit is contained in:
Lory Cloutier 2023-03-10 14:49:47 -06:00 committed by GitHub
parent ef14f3a560
commit 6de130fe39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 266 additions and 0 deletions

View file

@ -1581,6 +1581,8 @@ func (n *callPlanExpression) Evaluate(currentRow []interface{}) (interface{}, er
return n.EvaluateStr(currentRow)
case "DATETIMENAME":
return n.EvaluateDateTimeName(currentRow)
case "DATE_TRUNC":
return n.EvaluateDateTrunc(currentRow)
// time quantum functions
case "RANGEQ":
return n.EvaluateRangeQ(currentRow)

View file

@ -258,6 +258,8 @@ func (p *ExecutionPlanner) analyzeCallExpression(ctx context.Context, call *pars
return p.analyseFunctionStr(call, scope)
case "DATETIMENAME":
return p.analyzeFunctionDateTimeName(call, scope)
case "DATE_TRUNC":
return p.analyzeFunctionDateTrunc(call, scope)
// time quantum funtions
case "RANGEQ":
return p.analyzeFunctionRangeQ(call, scope)

View file

@ -144,6 +144,28 @@ func (p *ExecutionPlanner) analyzeFunctionDateTimeName(call *parser.Call, scope
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 {
@ -539,6 +561,67 @@ func (n *callPlanExpression) EvaluateDatetimeAdd(currentRow []interface{}) (inte
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 {

View file

@ -49,6 +49,7 @@ var TableTests []TableTest = []TableTest{
dateTimeNameTests,
toTimestampTests,
datetimeAddTests,
dateTruncTests,
datetimedifftests,
stringScalarFunctionsTests,

View file

@ -659,6 +659,184 @@ var datetimeAddTests = TableTest{
},
}
var dateTruncTests = TableTest{
Table: tbl(
"datetrunctests",
srcHdrs(
srcHdr("_id", fldTypeID),
srcHdr("ts", fldTypeTimestamp, "timeunit 'ns'"),
),
srcRows(
srcRow(int64(1), knownSubSecondTimestamp()),
),
),
SQLTests: []SQLTest{
{
name: "DateTruncIncorrectParamsCount",
SQLs: sqls(
"select date_trunc()",
),
ExpErr: "count of formal parameters (2) does not match count of actual parameters (0)",
},
{
name: "DateTruncTypeError",
SQLs: sqls(
"select date_trunc(1, 2)",
),
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'string'",
},
{
name: "DateTruncInvalidParam",
SQLs: sqls(
"select date_trunc('1', current_timestamp)",
),
ExpErr: "invalid value '1' for parameter 'interval'",
},
{
name: "DateTruncOnYear",
SQLs: sqls(
"select _id, date_trunc('yy', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnMonth",
SQLs: sqls(
"select _id, date_trunc('m', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnDay",
SQLs: sqls(
"select _id, date_trunc('d', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnHour",
SQLs: sqls(
"select _id, date_trunc('hh', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01T22"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnMinute",
SQLs: sqls(
"select _id, date_trunc('mi', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01T22:08"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnSecond",
SQLs: sqls(
"select _id, date_trunc('s', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01T22:08:41"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnMilliS",
SQLs: sqls(
"select _id, date_trunc('ms', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01T22:08:41.100"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnMicroS",
SQLs: sqls(
"select _id, date_trunc('us', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01T22:08:41.100200"),
),
Compare: CompareExactUnordered,
},
{
name: "DateTruncOnNanoS",
SQLs: sqls(
"select _id, date_trunc('ns', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012-11-01T22:08:41.100200300"),
),
Compare: CompareExactUnordered,
},
{
name: "VerifyTimeStamp",
SQLs: sqls(
"select _id, datetimename('ns', ts) from datetrunctests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "100200300"),
),
Compare: CompareExactUnordered,
},
},
}
var datetimedifftests = TableTest{
name: "DatetimeDiff",
Table: tbl("dttable",