mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
FB-1895: Implement DateTimeFromParts (#2296)
This commit is contained in:
parent
dc6cbad3fc
commit
909c62d44e
5 changed files with 155 additions and 3 deletions
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue