Skip to content
This repository was archived by the owner on Feb 21, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion sql3/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ const (

// insert errors

ErrInsertValueOutOfRange errors.Code = "ErrInsertValueOutOfRange"
ErrInsertValueOutOfRange errors.Code = "ErrInsertValueOutOfRange"
ErrUnexpectedTimeQuantumTupleLength errors.Code = "ErrUnexpectedTimeQuantumTupleLength"

// bulk insert errors

Expand Down Expand Up @@ -706,6 +707,13 @@ func NewErrInsertValueOutOfRange(line, col int, columnName string, rowNumber int
)
}

func NewErrUnexpectedTimeQuantumTupleLength(line, col int, columnName string, rowNumber int, badValue []interface{}, length int) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected Time Quantum Tuple Length

value %v out of range

I don't feel like these describe the same problem?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops - copy pasta

return errors.New(
ErrUnexpectedTimeQuantumTupleLength,
fmt.Sprintf("[%d:%d] inserting value into column '%s', row %d, value '%v' out of range", line, col, columnName, rowNumber, badValue),
)
}

// bulk insert

func NewErrReadingDatasource(line, col int, dataSource string, errorText string) error {
Expand Down
49 changes: 23 additions & 26 deletions sql3/planner/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ func coerceValue(sourceType parser.ExprDataType, targetType parser.ExprDataType,
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
}
return pql.NewDecimal(val*int64(math.Pow(10, float64(t.Scale))), t.Scale), nil

case *parser.DataTypeTimestamp:
val, ok := value.(int64)
if !ok {
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
}
tm := time.Unix(val, 0).UTC()
return tm, nil
}

case *parser.DataTypeID:
Expand All @@ -57,6 +65,14 @@ func coerceValue(sourceType parser.ExprDataType, targetType parser.ExprDataType,
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
}
return pql.NewDecimal(int64(val)*int64(math.Pow(10, float64(t.Scale))), t.Scale), nil

case *parser.DataTypeTimestamp:
val, ok := value.(int64)
if !ok {
return nil, sql3.NewErrInternalf("unexpected value type '%T'", value)
}
tm := time.Unix(val, 0).UTC()
return tm, nil
}

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

func (n *exprTupleLiteralPlanExpression) Evaluate(currentRow []interface{}) (interface{}, error) {
timestampEval, err := n.members[0].Evaluate(currentRow)
if err != nil {
return nil, err
}

// if it is a string, do a coercion
if val, ok := timestampEval.(string); ok {
if tm, err := timestampFromString(val); err != nil {
return nil, sql3.NewErrInvalidTypeCoercion(0, 0, val, n.members[0].Type().TypeDescription())
} else {
timestampEval = tm
result := make([]interface{}, len(n.members))
for i, m := range n.members {
v, err := m.Evaluate(currentRow)
if err != nil {
return nil, err
}
result[i] = v
}

setEval, err := n.members[1].Evaluate(currentRow)
if err != nil {
return nil, err
}

// nil if anything is nil
if timestampEval == nil || setEval == nil {
return nil, nil
}

return []interface{}{
timestampEval,
setEval,
}, nil
return result, nil
}

func (n *exprTupleLiteralPlanExpression) Type() parser.ExprDataType {
Expand Down
19 changes: 7 additions & 12 deletions sql3/planner/expressiontypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,12 +290,8 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par
if len(source.Members) != 2 {
return false
}
_, ok := source.Members[0].(*parser.DataTypeTimestamp)
if !ok {
_, ok = source.Members[0].(*parser.DataTypeString)
if !ok {
return false
}
if !typesAreAssignmentCompatible(parser.NewDataTypeTimestamp(), source.Members[0]) {
return false
}
_, ok = source.Members[1].(*parser.DataTypeStringSet)
if !ok {
Expand Down Expand Up @@ -324,12 +320,8 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par
if len(source.Members) != 2 {
return false
}
_, ok := source.Members[0].(*parser.DataTypeTimestamp)
if !ok {
_, ok = source.Members[0].(*parser.DataTypeString)
if !ok {
return false
}
if !typesAreAssignmentCompatible(parser.NewDataTypeTimestamp(), source.Members[0]) {
return false
}
_, ok = source.Members[1].(*parser.DataTypeIDSet)
if !ok {
Expand All @@ -354,6 +346,9 @@ func typesAreAssignmentCompatible(targetType parser.ExprDataType, sourceType par
switch sourceType.(type) {
case *parser.DataTypeTimestamp:
return true
case *parser.DataTypeInt:
//could be a int convertable to a date
return true
case *parser.DataTypeString:
//could be a string parseable as a date
return true
Expand Down
56 changes: 55 additions & 1 deletion sql3/planner/opinsert.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/featurebasedb/featurebase/v3/dax"
"github.com/featurebasedb/featurebase/v3/pql"
"github.com/featurebasedb/featurebase/v3/sql3"
"github.com/featurebasedb/featurebase/v3/sql3/parser"
"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -253,9 +254,9 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) {
}

case pilosa.FieldTypeTime:
row.Time = qbatchTime
switch v := eval.(type) {
case []int64:
row.Time = qbatchTime
uint64s := make([]uint64, len(v))
for i := range v {
if v[i] < 0 {
Expand All @@ -264,6 +265,59 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) {
uint64s[i] = uint64(v[i])
}
row.Values[posVals[idx]] = uint64s

case []interface{}:
// it's a tuple, check length
if len(v) != 2 {
return nil, sql3.NewErrUnexpectedTimeQuantumTupleLength(0, 0, columnName, rowNumber+1, v, len(v))
}

tupleType, ok := iv.Type().(*parser.DataTypeTuple)
if !ok {
return nil, sql3.NewErrInternalf("unexpected tuple type '%T'", v[0])
}

// first member must be a timestamp or coercable as one
cval, err := coerceValue(tupleType.Members[0], parser.NewDataTypeTimestamp(), v[0], parser.Pos{Line: 0, Column: 0})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not have a valid position we can use here?

also... should the timestamp be the first value? my intuition would have been that the timestamp was the last value in the tuple. did we specify this anywhere? is there a thing we're doing this to be compatible with?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely arbitrary. If someone can point to prior art where it's the other way, happy to change it.

if err != nil {
return nil, err
}
tval, ok := cval.(time.Time)
if !ok {
return nil, sql3.NewErrInternalf("unexpected tuple time value type '%T'", v[0])
}
var qrowTime fbbatch.QuantizedTime
qrowTime.Set(tval)
row.Time = qrowTime

// second member must be a set of the correct type
targetCol := i.targetColumns[idx]

switch targetCol.Type().(type) {
case *parser.DataTypeStringSetQuantum:
sval, ok := v[1].([]string)
if !ok {
return nil, sql3.NewErrInternalf("string set type expected '%T'", v[1])
}
row.Values[posVals[idx]] = sval

case *parser.DataTypeIDSetQuantum:
sval, ok := v[1].([]int64)
if !ok {
return nil, sql3.NewErrInternalf("id set type expected '%T'", v[1])
}
uint64s := make([]uint64, len(sval))
for i := range sval {
if sval[i] < 0 {
return nil, sql3.NewErrInternalf("converting negative slice value to uint64: %d", sval[i])
}
uint64s[i] = uint64(sval[i])
}
row.Values[posVals[idx]] = uint64s

default:
return nil, sql3.NewErrInternalf("unexpected set type '%T'", targetCol.Type())
}
default:
row.Values[posVals[idx]] = eval
}
Expand Down
6 changes: 0 additions & 6 deletions sql3/test/defs/defs_date_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ var datePartTests = TableTest{
),
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'string'",
},
{
SQLs: sqls(
"select datepart('1', 2)",
),
ExpErr: "an expression of type 'int' cannot be passed to a parameter of type 'timestamp'",
},
{
SQLs: sqls(
"select datepart('1', current_timestamp)",
Expand Down
25 changes: 24 additions & 1 deletion sql3/test/defs/defs_timequantum.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,36 @@ var timeQuantumInsertTest = TableTest{
srcHdrs(
srcHdr("_id", fldTypeID),
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
srcHdr("ss1", fldTypeStringSet, "timequantum 'YMD'"),
srcHdr("ids1", fldTypeIDSet, "timequantum 'YMD'"),
),
),
SQLTests: []SQLTest{
{
SQLs: sqls(
"insert into time_quantum_insert (_id, i1, ids1) values (1, 1, [1])",
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, ['1'], [1])",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
},
{
SQLs: sqls(
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, {['1']}, {[1]})",
),
ExpErr: "an expression of type 'tuple(stringset)' cannot be assigned to type 'stringset'",
},
{
SQLs: sqls(
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, {1676649734, ['1']}, {1676649734, [1]})",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Compare: CompareExactUnordered,
},
{
SQLs: sqls(
"insert into time_quantum_insert (_id, i1, ss1, ids1) values (1, 1, {'2022-01-01T00:00:00Z', ['1']}, {'2022-01-01T00:00:00Z', [1]})",
),
ExpHdrs: hdrs(),
ExpRows: rows(),
Expand Down