Skip to content
This repository was archived by the owner on Feb 21, 2024. It is now read-only.

Commit c749e07

Browse files
authored
add support for time quantum inserts with explicit timestamps (fb-1558) (#2262)
* added support for time quantum inserts with explicit timestamps * fixed copy pasta
1 parent 66e079f commit c749e07

File tree

6 files changed

+118
-47
lines changed

6 files changed

+118
-47
lines changed

sql3/errors.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ const (
109109

110110
// insert errors
111111

112-
ErrInsertValueOutOfRange errors.Code = "ErrInsertValueOutOfRange"
112+
ErrInsertValueOutOfRange errors.Code = "ErrInsertValueOutOfRange"
113+
ErrUnexpectedTimeQuantumTupleLength errors.Code = "ErrUnexpectedTimeQuantumTupleLength"
113114

114115
// bulk insert errors
115116

@@ -706,6 +707,13 @@ func NewErrInsertValueOutOfRange(line, col int, columnName string, rowNumber int
706707
)
707708
}
708709

710+
func NewErrUnexpectedTimeQuantumTupleLength(line, col int, columnName string, rowNumber int, badValue []interface{}, length int) error {
711+
return errors.New(
712+
ErrUnexpectedTimeQuantumTupleLength,
713+
fmt.Sprintf("[%d:%d] inserting value into column '%s', row %d, value '%v' out of range", line, col, columnName, rowNumber, badValue),
714+
)
715+
}
716+
709717
// bulk insert
710718

711719
func NewErrReadingDatasource(line, col int, dataSource string, errorText string) error {

sql3/planner/expression.go

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ func coerceValue(sourceType parser.ExprDataType, targetType parser.ExprDataType,
4141
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
4242
}
4343
return pql.NewDecimal(val*int64(math.Pow(10, float64(t.Scale))), t.Scale), nil
44+
45+
case *parser.DataTypeTimestamp:
46+
val, ok := value.(int64)
47+
if !ok {
48+
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
49+
}
50+
tm := time.Unix(val, 0).UTC()
51+
return tm, nil
4452
}
4553

4654
case *parser.DataTypeID:
@@ -57,6 +65,14 @@ func coerceValue(sourceType parser.ExprDataType, targetType parser.ExprDataType,
5765
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
5866
}
5967
return pql.NewDecimal(int64(val)*int64(math.Pow(10, float64(t.Scale))), t.Scale), nil
68+
69+
case *parser.DataTypeTimestamp:
70+
val, ok := value.(int64)
71+
if !ok {
72+
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
73+
}
74+
tm := time.Unix(val, 0).UTC()
75+
return tm, nil
6076
}
6177

6278
case *parser.DataTypeDecimal:
@@ -2435,34 +2451,15 @@ func newExprTupleLiteralPlanExpression(members []types.PlanExpression, dataType
24352451
}
24362452

24372453
func (n *exprTupleLiteralPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
2438-
timestampEval, err := n.members[0].Evaluate(currentRow)
2439-
if err != nil {
2440-
return nil, err
2441-
}
2442-
2443-
// if it is a string, do a coercion
2444-
if val, ok := timestampEval.(string); ok {
2445-
if tm, err := timestampFromString(val); err != nil {
2446-
return nil, sql3.NewErrInvalidTypeCoercion(0, 0, val, n.members[0].Type().TypeDescription())
2447-
} else {
2448-
timestampEval = tm
2454+
result := make([]interface{}, len(n.members))
2455+
for i, m := range n.members {
2456+
v, err := m.Evaluate(currentRow)
2457+
if err != nil {
2458+
return nil, err
24492459
}
2460+
result[i] = v
24502461
}
2451-
2452-
setEval, err := n.members[1].Evaluate(currentRow)
2453-
if err != nil {
2454-
return nil, err
2455-
}
2456-
2457-
// nil if anything is nil
2458-
if timestampEval == nil || setEval == nil {
2459-
return nil, nil
2460-
}
2461-
2462-
return []interface{}{
2463-
timestampEval,
2464-
setEval,
2465-
}, nil
2462+
return result, nil
24662463
}
24672464

24682465
func (n *exprTupleLiteralPlanExpression) Type() parser.ExprDataType {

sql3/planner/expressiontypes.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -290,12 +290,8 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par
290290
if len(source.Members) != 2 {
291291
return false
292292
}
293-
_, ok := source.Members[0].(*parser.DataTypeTimestamp)
294-
if !ok {
295-
_, ok = source.Members[0].(*parser.DataTypeString)
296-
if !ok {
297-
return false
298-
}
293+
if !typesAreAssignmentCompatible(parser.NewDataTypeTimestamp(), source.Members[0]) {
294+
return false
299295
}
300296
_, ok = source.Members[1].(*parser.DataTypeStringSet)
301297
if !ok {
@@ -324,12 +320,8 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par
324320
if len(source.Members) != 2 {
325321
return false
326322
}
327-
_, ok := source.Members[0].(*parser.DataTypeTimestamp)
328-
if !ok {
329-
_, ok = source.Members[0].(*parser.DataTypeString)
330-
if !ok {
331-
return false
332-
}
323+
if !typesAreAssignmentCompatible(parser.NewDataTypeTimestamp(), source.Members[0]) {
324+
return false
333325
}
334326
_, ok = source.Members[1].(*parser.DataTypeIDSet)
335327
if !ok {
@@ -354,6 +346,9 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par
354346
switch sourceType.(type) {
355347
case *parser.DataTypeTimestamp:
356348
return true
349+
case *parser.DataTypeInt:
350+
//could be a int convertable to a date
351+
return true
357352
case *parser.DataTypeString:
358353
//could be a string parseable as a date
359354
return true

sql3/planner/opinsert.go

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/featurebasedb/featurebase/v3/dax"
1414
"github.com/featurebasedb/featurebase/v3/pql"
1515
"github.com/featurebasedb/featurebase/v3/sql3"
16+
"github.com/featurebasedb/featurebase/v3/sql3/parser"
1617
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
1718
"github.com/pkg/errors"
1819
)
@@ -253,9 +254,9 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) {
253254
}
254255

255256
case pilosa.FieldTypeTime:
256-
row.Time = qbatchTime
257257
switch v := eval.(type) {
258258
case []int64:
259+
row.Time = qbatchTime
259260
uint64s := make([]uint64, len(v))
260261
for i := range v {
261262
if v[i] < 0 {
@@ -264,6 +265,59 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) {
264265
uint64s[i] = uint64(v[i])
265266
}
266267
row.Values[posVals[idx]] = uint64s
268+
269+
case []interface{}:
270+
// it's a tuple, check length
271+
if len(v) != 2 {
272+
return nil, sql3.NewErrUnexpectedTimeQuantumTupleLength(0, 0, columnName, rowNumber+1, v, len(v))
273+
}
274+
275+
tupleType, ok := iv.Type().(*parser.DataTypeTuple)
276+
if !ok {
277+
return nil, sql3.NewErrInternalf("unexpected tuple type '%T'", v[0])
278+
}
279+
280+
// first member must be a timestamp or coercable as one
281+
cval, err := coerceValue(tupleType.Members[0], parser.NewDataTypeTimestamp(), v[0], parser.Pos{Line: 0, Column: 0})
282+
if err != nil {
283+
return nil, err
284+
}
285+
tval, ok := cval.(time.Time)
286+
if !ok {
287+
return nil, sql3.NewErrInternalf("unexpected tuple time value type '%T'", v[0])
288+
}
289+
var qrowTime fbbatch.QuantizedTime
290+
qrowTime.Set(tval)
291+
row.Time = qrowTime
292+
293+
// second member must be a set of the correct type
294+
targetCol := i.targetColumns[idx]
295+
296+
switch targetCol.Type().(type) {
297+
case *parser.DataTypeStringSetQuantum:
298+
sval, ok := v[1].([]string)
299+
if !ok {
300+
return nil, sql3.NewErrInternalf("string set type expected '%T'", v[1])
301+
}
302+
row.Values[posVals[idx]] = sval
303+
304+
case *parser.DataTypeIDSetQuantum:
305+
sval, ok := v[1].([]int64)
306+
if !ok {
307+
return nil, sql3.NewErrInternalf("id set type expected '%T'", v[1])
308+
}
309+
uint64s := make([]uint64, len(sval))
310+
for i := range sval {
311+
if sval[i] < 0 {
312+
return nil, sql3.NewErrInternalf("converting negative slice value to uint64: %d", sval[i])
313+
}
314+
uint64s[i] = uint64(sval[i])
315+
}
316+
row.Values[posVals[idx]] = uint64s
317+
318+
default:
319+
return nil, sql3.NewErrInternalf("unexpected set type '%T'", targetCol.Type())
320+
}
267321
default:
268322
row.Values[posVals[idx]] = eval
269323
}

sql3/test/defs/defs_date_functions.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ var datePartTests = TableTest{
2828
),
2929
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'string'",
3030
},
31-
{
32-
SQLs: sqls(
33-
"select datepart('1', 2)",
34-
),
35-
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'timestamp'",
36-
},
3731
{
3832
SQLs: sqls(
3933
"select datepart('1', current_timestamp)",

sql3/test/defs/defs_timequantum.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,36 @@ var timeQuantumInsertTest = TableTest{
77
srcHdrs(
88
srcHdr("_id", fldTypeID),
99
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
10+
srcHdr("ss1", fldTypeStringSet, "timequantum 'YMD'"),
1011
srcHdr("ids1", fldTypeIDSet, "timequantum 'YMD'"),
1112
),
1213
),
1314
SQLTests: []SQLTest{
1415
{
1516
SQLs: sqls(
16-
"insert into time_quantum_insert (_id, i1, ids1) values (1, 1, [1])",
17+
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, ['1'], [1])",
18+
),
19+
ExpHdrs: hdrs(),
20+
ExpRows: rows(),
21+
Compare: CompareExactUnordered,
22+
},
23+
{
24+
SQLs: sqls(
25+
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, {['1']}, {[1]})",
26+
),
27+
ExpErr: "an expression of type 'tuple(stringset)' cannot be assigned to type 'stringset'",
28+
},
29+
{
30+
SQLs: sqls(
31+
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, {1676649734, ['1']}, {1676649734, [1]})",
32+
),
33+
ExpHdrs: hdrs(),
34+
ExpRows: rows(),
35+
Compare: CompareExactUnordered,
36+
},
37+
{
38+
SQLs: sqls(
39+
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, {'2022-01-01T00:00:00Z', ['1']}, {'2022-01-01T00:00:00Z', [1]})",
1740
),
1841
ExpHdrs: hdrs(),
1942
ExpRows: rows(),

0 commit comments

Comments
 (0)