mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 10:54:59 +00:00
tightened up is/is not null filter expressions (FB-1741) (#2260)
Covers tightening up handling filter expressions that contain is/is not null ops. These filters may have to be translated into PQL calls to be passed to the executor and even though sql3 language supports nullability for any data type, currently only BSI fields are nullable at the storage engine level (there is a ticket to add support for non-BSI field here FB-1689: IS SQL Argument returns incorrect error) so when these fields are used in filter conditions we need to handle BSI and non-BSI fields differently.
This commit is contained in:
parent
fb40cdc2cb
commit
b32c33c992
5 changed files with 222 additions and 4 deletions
|
|
@ -46,6 +46,8 @@ const (
|
|||
ErrLiteralEmptySetNotAllowed errors.Code = "ErrLiteralEmptySetNotAllowed"
|
||||
ErrLiteralEmptyTupleNotAllowed errors.Code = "ErrLiteralEmptyTupleNotAllowed"
|
||||
ErrSetLiteralMustContainIntOrString errors.Code = "ErrSetLiteralMustContainIntOrString"
|
||||
ErrInvalidColumnInFilterExpression errors.Code = "ErrInvalidColumnInFilterExpression"
|
||||
ErrInvalidTypeInFilterExpression errors.Code = "ErrInvalidTypeInFilterExpression"
|
||||
|
||||
ErrTypeAssignmentIncompatible errors.Code = "ErrTypeAssignmentIncompatible"
|
||||
|
||||
|
|
@ -198,6 +200,20 @@ func NewErrSetLiteralMustContainIntOrString(line, col int) error {
|
|||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidColumnInFilterExpression(line, col int, column string, op string) error {
|
||||
return errors.New(
|
||||
ErrInvalidColumnInFilterExpression,
|
||||
fmt.Sprintf("[%d:%d] '%s' column cannot be used in a %s filter expression", line, col, column, op),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrInvalidTypeInFilterExpression(line, col int, typeName string, op string) error {
|
||||
return errors.New(
|
||||
ErrInvalidTypeInFilterExpression,
|
||||
fmt.Sprintf("[%d:%d] unsupported type '%s' for %s filter expression", line, col, typeName, op),
|
||||
)
|
||||
}
|
||||
|
||||
func NewErrLiteralEmptyTupleNotAllowed(line, col int) error {
|
||||
return errors.New(
|
||||
ErrLiteralEmptyTupleNotAllowed,
|
||||
|
|
|
|||
|
|
@ -193,6 +193,38 @@ func (p *ExecutionPlanner) generatePQLCallFromBinaryExpr(ctx context.Context, ex
|
|||
case parser.IN, parser.NOTIN:
|
||||
return nil, sql3.NewErrInternal("IN operator is not supported")
|
||||
|
||||
case parser.IS, parser.ISNOT:
|
||||
lhs, ok := expr.lhs.(*qualifiedRefPlanExpression)
|
||||
if !ok {
|
||||
return nil, sql3.NewErrInternalf("unexpected lhs %T", expr.lhs)
|
||||
}
|
||||
|
||||
pqlOp := pql.EQ
|
||||
if op == parser.ISNOT {
|
||||
pqlOp = pql.NEQ
|
||||
}
|
||||
switch typ := expr.lhs.Type().(type) {
|
||||
case *parser.DataTypeID:
|
||||
if strings.EqualFold(lhs.columnName, "_id") {
|
||||
return nil, sql3.NewErrInvalidColumnInFilterExpression(0, 0, "_id", "is/is not null")
|
||||
}
|
||||
return nil, sql3.NewErrInvalidTypeInFilterExpression(0, 0, typ.TypeName(), "is/is not null")
|
||||
|
||||
case *parser.DataTypeInt, *parser.DataTypeDecimal, *parser.DataTypeTimestamp:
|
||||
return &pql.Call{
|
||||
Name: "Row",
|
||||
Args: map[string]interface{}{
|
||||
lhs.columnName: &pql.Condition{
|
||||
Op: pqlOp,
|
||||
Value: nil,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, sql3.NewErrInvalidTypeInFilterExpression(0, 0, typ.TypeName(), "is/is not null")
|
||||
}
|
||||
|
||||
case parser.BETWEEN, parser.NOTBETWEEN:
|
||||
return nil, sql3.NewErrInternal("BETWEEN operator is not supported")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"hash/maphash"
|
||||
"log"
|
||||
|
||||
"github.com/molecula/featurebase/v3/errors"
|
||||
"github.com/molecula/featurebase/v3/sql3"
|
||||
|
|
@ -336,6 +335,5 @@ func groupingKeyHash(ctx context.Context, groupByExprs []types.PlanExpression, r
|
|||
rowKeys[i] = v
|
||||
}
|
||||
result := hash.Sum64()
|
||||
log.Printf("Hash %v, %v", result, rowKeys)
|
||||
return result, rowKeys, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,6 +316,9 @@ var tableTests []tableTest = []tableTest{
|
|||
nullTests,
|
||||
notNullTests,
|
||||
|
||||
//null filter tests
|
||||
nullFilterTests,
|
||||
|
||||
//between tests
|
||||
betweenTests,
|
||||
notBetweenTests,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package sql3_test
|
||||
|
||||
//NULL tests
|
||||
// NULL tests
|
||||
var nullTests = tableTest{
|
||||
table: tbl(
|
||||
"null_all_types",
|
||||
|
|
@ -144,7 +144,7 @@ var nullTests = tableTest{
|
|||
},
|
||||
}
|
||||
|
||||
//NOT NULL tests
|
||||
// NOT NULL tests
|
||||
var notNullTests = tableTest{
|
||||
table: tbl(
|
||||
"not_null_all_types",
|
||||
|
|
@ -275,3 +275,172 @@ var notNullTests = tableTest{
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
// NULL filter condition tests
|
||||
var nullFilterTests = tableTest{
|
||||
table: tbl(
|
||||
"null_filter_all_types",
|
||||
srcHdrs(
|
||||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("i", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("i1", fldTypeInt, "min 0", "max 1000"),
|
||||
srcHdr("b1", fldTypeBool),
|
||||
srcHdr("d1", fldTypeDecimal2),
|
||||
srcHdr("id1", fldTypeID),
|
||||
srcHdr("ids1", fldTypeIDSet),
|
||||
srcHdr("s1", fldTypeString),
|
||||
srcHdr("ss1", fldTypeStringSet),
|
||||
srcHdr("t1", fldTypeTimestamp),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), int64(1), nil, nil, nil, nil, nil, nil, nil, nil),
|
||||
srcRow(int64(2), int64(1), int64(10), bool(true), float64(10), int64(20), []int64{101}, string("foo"), []string{"GET", "POST"}, knownTimestamp()),
|
||||
),
|
||||
),
|
||||
sqlTests: []sqlTest{
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where _id is null",
|
||||
),
|
||||
expErr: "'_id' column cannot be used in a is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where _id is not null",
|
||||
),
|
||||
expErr: "'_id' column cannot be used in a is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where i1 is null",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where i1 is not null",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where b1 is null",
|
||||
),
|
||||
expErr: "unsupported type 'BOOL' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where b1 is not null",
|
||||
),
|
||||
expErr: "unsupported type 'BOOL' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where d1 is null",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where d1 is not null",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where id1 is null",
|
||||
),
|
||||
expErr: "unsupported type 'ID' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where id1 is not null",
|
||||
),
|
||||
expErr: "unsupported type 'ID' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where ids1 is null",
|
||||
),
|
||||
expErr: "unsupported type 'IDSET' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where ids1 is not null",
|
||||
),
|
||||
expErr: "unsupported type 'IDSET' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where s1 is null",
|
||||
),
|
||||
expErr: "unsupported type 'STRING' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where s1 is not null",
|
||||
),
|
||||
expErr: "unsupported type 'STRING' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where ss1 is null",
|
||||
),
|
||||
expErr: "unsupported type 'STRINGSET' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where ss1 is not null",
|
||||
),
|
||||
expErr: "unsupported type 'STRINGSET' for is/is not null filter expression",
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where t1 is null",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(1)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
{
|
||||
sqls: sqls(
|
||||
"select _id from null_filter_all_types where t1 is not null",
|
||||
),
|
||||
expHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
),
|
||||
expRows: rows(
|
||||
row(int64(2)),
|
||||
),
|
||||
compare: compareExactUnordered,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue