diff --git a/etcd/embed.go b/etcd/embed.go index 1525bad31..c4bfcaeca 100644 --- a/etcd/embed.go +++ b/etcd/embed.go @@ -228,10 +228,10 @@ func NewEtcd(opt Options, logger logger.Logger, replicas int, version string) *E if e.options.HeartbeatTTL == 0 { e.options.HeartbeatTTL = 5 // seconds - // !! DEBUG - // e.options.HeartbeatTTL = 3600 // seconds - // !! DEBUG } + // !! DEBUG + // e.options.HeartbeatTTL = 3600 // seconds + // !! DEBUG return e } diff --git a/sql3/errors.go b/sql3/errors.go index c0cb070ed..0790ef1c4 100644 --- a/sql3/errors.go +++ b/sql3/errors.go @@ -89,6 +89,10 @@ const ( ErrParameterTypeMistmatch errors.Code = "ErrParameterTypeMistmatch" ErrCallParameterValueInvalid errors.Code = "ErrCallParameterValueInvalid" + // insert errors + + ErrInsertValueOutOfRange errors.Code = "ErrInsertValueOutOfRange" + // bulk insert errors ErrReadingDatasource errors.Code = "ErrReadingDatasource" @@ -576,6 +580,15 @@ func NewErrCallParameterValueInvalid(line, col int, badParameterValue string, pa ) } +// insert + +func NewErrInsertValueOutOfRange(line, col int, columnName string, rowNumber int, badValue interface{}) error { + return errors.New( + ErrInsertValueOutOfRange, + 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 { diff --git a/sql3/planner/opinsert.go b/sql3/planner/opinsert.go index 91189d5a7..b12b1cd2c 100644 --- a/sql3/planner/opinsert.go +++ b/sql3/planner/opinsert.go @@ -10,6 +10,7 @@ import ( pilosa "github.com/molecula/featurebase/v3" fbbatch "github.com/molecula/featurebase/v3/batch" + "github.com/molecula/featurebase/v3/pql" "github.com/molecula/featurebase/v3/sql3" "github.com/molecula/featurebase/v3/sql3/planner/types" "github.com/pkg/errors" @@ -184,7 +185,7 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) { // record ID ("_id") since that's stored in row.ID. row.Values = make([]interface{}, len(i.targetColumns)-1) - for _, tuple := range i.insertValues { + for rowNumber, tuple := range i.insertValues { // Evaluate and set the record ID. if eval, err := tuple[posID].Evaluate(nil); err != nil { return nil, errors.Wrapf(err, "evaluating record id: %v", tuple[posID]) @@ -219,6 +220,8 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) { return nil, errors.Wrapf(err, "evaluating tuple value: %v", iv) } + columnName := idxInfo.Fields[posVals[idx]].Name + // batch.Add does not typically look at field type to determine how // to handle a particular value in a row. Instead, it uses value // type. As an example, if the value type is int64, then batch.Add @@ -255,6 +258,32 @@ func (i *insertRowIter) Next(ctx context.Context) (types.Row, error) { row.Values[posVals[idx]] = eval } + case pilosa.FieldTypeInt: + if eval != nil { + v, ok := eval.(int64) + if !ok { + return nil, sql3.NewErrInternalf("unexpected type %v", eval) + } + // check the min and max constraints here + if v < opts.Min.ToInt64(0) || v > opts.Max.ToInt64(0) { + return nil, sql3.NewErrInsertValueOutOfRange(0, 0, columnName, rowNumber+1, v) + } + } + row.Values[posVals[idx]] = eval + + case pilosa.FieldTypeDecimal: + if eval != nil { + v, ok := eval.(pql.Decimal) + if !ok { + return nil, sql3.NewErrInternalf("unexpected type %v", eval) + } + // check the min and max constraints here + if v.LessThan(opts.Min) || v.GreaterThan(opts.Max) { + return nil, sql3.NewErrInsertValueOutOfRange(0, 0, columnName, rowNumber+1, v) + } + } + row.Values[posVals[idx]] = eval + case pilosa.FieldTypeTimestamp: switch v := eval.(type) { diff --git a/sql3/test/defs/defs_inserts.go b/sql3/test/defs/defs_inserts.go index a839ea909..4de033f24 100644 --- a/sql3/test/defs/defs_inserts.go +++ b/sql3/test/defs/defs_inserts.go @@ -9,7 +9,7 @@ var insertTest = TableTest{ srcHdr("b", fldTypeInt, "min 0", "max 1000"), srcHdr("s", fldTypeString), srcHdr("bl", fldTypeBool), - srcHdr("d", fldTypeDecimal2), + srcHdr("d", fldTypeDecimal2, "min 0", "max 1000"), srcHdr("event", fldTypeStringSet), srcHdr("ievent", fldTypeIDSet), ), @@ -124,5 +124,33 @@ var insertTest = TableTest{ ), ExpErr: "an expression of type 'stringset' cannot be assigned to type 'idset'", }, + { + name: "min constraint", + SQLs: sqls( + "insert into testinsert (_id, a) values (400, -1)", + ), + ExpErr: "inserting value into column 'a', row 1, value '-1' out of range", + }, + { + name: "max constraint", + SQLs: sqls( + "insert into testinsert (_id, a) values (400, 1001)", + ), + ExpErr: "inserting value into column 'a', row 1, value '1001' out of range", + }, + { + name: "min constraint decimal", + SQLs: sqls( + "insert into testinsert (_id, d) values (400, -1.00)", + ), + ExpErr: "inserting value into column 'd', row 1, value '-1' out of range", + }, + { + name: "max constraint decimal", + SQLs: sqls( + "insert into testinsert (_id, d) values (400, 1001.00)", + ), + ExpErr: "inserting value into column 'd', row 1, value '1001' out of range", + }, }, } diff --git a/sql3/test/defs/types.go b/sql3/test/defs/types.go index 48b5ca825..2964ca9a6 100644 --- a/sql3/test/defs/types.go +++ b/sql3/test/defs/types.go @@ -274,6 +274,7 @@ func (s source) insertInto(t *testing.T, rowSets []int) string { for _, rowSet := range rowSets { ii += sourceRows(s.rows[rowSet]).insertTuples(t) } + log.Printf("INSERT: %s", ii) return ii }