Add datetimename function to SQL3 (#2293)

FB-1896
Added the datetimename function, which returns parts of a timestamp
as strings. Month and day of the week are named ("January", "Monday")
while others are returned as a string of digits ("2023").
Added tests to the test definitions.
This commit is contained in:
Lory Cloutier 2023-03-07 12:58:44 -06:00 committed by GitHub
parent 05ebdd15f0
commit 7ea4135ecf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 176 additions and 7 deletions

View file

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

View file

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

View file

@ -1,6 +1,7 @@
package planner
import (
"fmt"
"strings"
"time"
@ -103,6 +104,29 @@ func (p *ExecutionPlanner) analyzeFunctionDatetimeAdd(call *parser.Call, scope p
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 (n *callPlanExpression) EvaluateDatepart(currentRow []interface{}) (interface{}, error) {
intervalEval, err := n.args[0].Evaluate(currentRow)
if err != nil {
@ -174,10 +198,10 @@ func (n *callPlanExpression) EvaluateDatepart(currentRow []interface{}) (interfa
return int64(date.Nanosecond() / 1000000), nil
case intervalMicrosecond:
return int64((date.Nanosecond() % 1000000) / 1000), nil
return int64(date.Nanosecond() / 1000), nil
case intervalNanosecond:
return int64(date.Nanosecond() % 1000), nil
return int64(date.Nanosecond()), nil
default:
return nil, sql3.NewErrCallParameterValueInvalid(0, 0, interval, "interval")
@ -232,6 +256,85 @@ func (n *callPlanExpression) EvaluateToTimestamp(currentRow []interface{}) (inte
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)

View file

@ -46,8 +46,10 @@ var TableTests []TableTest = []TableTest{
setFunctionTests,
setParameterTests,
datePartTests,
dateTimeNameTests,
toTimestampTests,
datetimeAddTests,
stringScalarFunctionsTests,
insertTest,

View file

@ -175,7 +175,7 @@ var datePartTests = TableTest{
hdr("", fldTypeInt),
),
ExpRows: rows(
row(int64(1), int64(200)),
row(int64(1), int64(100200)),
),
Compare: CompareExactUnordered,
},
@ -188,7 +188,7 @@ var datePartTests = TableTest{
hdr("", fldTypeInt),
),
ExpRows: rows(
row(int64(1), int64(300)),
row(int64(1), int64(100200300)),
),
Compare: CompareExactUnordered,
},
@ -269,6 +269,66 @@ var toTimestampTests = TableTest{
},
}
var dateTimeNameTests = TableTest{
Table: tbl(
"datetimenametests",
srcHdrs(
srcHdr("_id", fldTypeID),
srcHdr("ts", fldTypeTimestamp),
),
srcRows(
srcRow(int64(1), knownTimestamp()),
),
),
SQLTests: []SQLTest{
// dateTimeName tests
// make sure returning a string as the year still works
// if this works then the other parts converted to strings of digits should also work
{
SQLs: sqls(
"select _id, datetimename('yy', ts) from dateparttests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "2012"),
),
Compare: CompareExactUnordered,
},
//check to make sure it gets a month name correctly
{
SQLs: sqls(
"select _id, datetimename('m', ts) from dateparttests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "November"),
),
Compare: CompareExactUnordered,
},
// check to make sure it gets a day of the week correctly
{
SQLs: sqls(
"select _id, datetimename('w', ts) from dateparttests",
),
ExpHdrs: hdrs(
hdr("_id", fldTypeID),
hdr("", fldTypeString),
),
ExpRows: rows(
row(int64(1), "Thursday"),
),
Compare: CompareExactUnordered,
},
},
}
// datetimeAdd tests
var datetimeAddTests = TableTest{
@ -420,7 +480,7 @@ var datetimeAddTests = TableTest{
hdr("", fldTypeInt),
),
ExpRows: rows(
row(int64(1), int64(201)),
row(int64(1), int64(100201)),
),
Compare: CompareExactUnordered,
},
@ -433,7 +493,7 @@ var datetimeAddTests = TableTest{
hdr("", fldTypeInt),
),
ExpRows: rows(
row(int64(1), int64(301)),
row(int64(1), int64(100200301)),
),
Compare: CompareExactUnordered,
},
@ -462,7 +522,7 @@ var datetimeAddTests = TableTest{
hdr("b", fldTypeInt),
),
ExpRows: rows(
row(int64(1), int64(0), int64(201)),
row(int64(1), int64(100201000), int64(100201)),
),
Compare: CompareExactUnordered,
},