diff --git a/http_handler.go b/http_handler.go index 636b8d238..cf4caf5ba 100644 --- a/http_handler.go +++ b/http_handler.go @@ -1379,7 +1379,7 @@ func (h *Handler) handlePostSQL(w http.ResponseWriter, r *http.Request) { // Write response back to client. w.Header().Set("Content-Type", "application/json") - // the pandas data frame format in json per https://molecula.atlassian.net/wiki/spaces/MOLECULA/pages/540999700/Queries + // the pandas data frame format in json // Opening bracket. w.Write([]byte("{")) diff --git a/sql3/planner/expression.go b/sql3/planner/expression.go index 60a27208d..f7e6186e1 100644 --- a/sql3/planner/expression.go +++ b/sql3/planner/expression.go @@ -28,7 +28,11 @@ func coerceValue(sourceType parser.ExprDataType, targetType parser.ExprDataType, return value, nil case *parser.DataTypeID: - return value, nil + val, ok := value.(int64) + if !ok { + return nil, sql3.NewErrInternalf("unexpected value type '%T'", value) + } + return val, nil case *parser.DataTypeDecimal: val, ok := value.(int64) diff --git a/sql3/planner/oppqlaggregate.go b/sql3/planner/oppqlaggregate.go index 84e89b84a..8589ea7a8 100644 --- a/sql3/planner/oppqlaggregate.go +++ b/sql3/planner/oppqlaggregate.go @@ -140,14 +140,39 @@ func (i *pqlAggregateRowIter) Next(ctx context.Context) (types.Row, error) { case types.AGGREGATE_COUNT: if cond == nil { - cond = &pql.Call{Name: "All"} + // COUNT() should ignore null values + // if the data type of the expression supports an existence bitmap for + // the underlying FeatureBase data type use it to eliminate nulls from the aggregate + switch expr.dataType.(type) { + case *parser.DataTypeInt, *parser.DataTypeTimestamp, *parser.DataTypeDecimal: + cond = &pql.Call{ + Name: "Row", + Args: map[string]interface{}{ + expr.columnName: &pql.Condition{Op: pql.NEQ, Value: nil}, + }, + } + default: + cond = &pql.Call{Name: "All"} + } } - call = &pql.Call{Name: "Count", Children: []*pql.Call{cond}} case types.AGGREGATE_AVG: if cond == nil { - cond = &pql.Call{Name: "All"} + // COUNT() should ignore null values + // if the data type of the expression supports an existence bitmap for + // the underlying FeatureBase data type use it to eliminate nulls from the aggregate + switch expr.dataType.(type) { + case *parser.DataTypeInt, *parser.DataTypeTimestamp, *parser.DataTypeDecimal: + cond = &pql.Call{ + Name: "Row", + Args: map[string]interface{}{ + expr.columnName: &pql.Condition{Op: pql.NEQ, Value: nil}, + }, + } + default: + cond = &pql.Call{Name: "All"} + } } call = &pql.Call{ diff --git a/sql3/sql_defs_aggregate_test.go b/sql3/sql_defs_aggregate_test.go index d52b11007..d11910f23 100644 --- a/sql3/sql_defs_aggregate_test.go +++ b/sql3/sql_defs_aggregate_test.go @@ -5,7 +5,7 @@ import ( "github.com/molecula/featurebase/v3/sql3/parser" ) -//aggregate function tests +// aggregate function tests var countTests = tableTest{ table: tbl( "count_test", @@ -50,6 +50,19 @@ var countTests = tableTest{ ), compare: compareExactUnordered, }, + { + sqls: sqls( + "SELECT COUNT(i1) as a, COUNT(i2) as b FROM count_test", + ), + expHdrs: hdrs( + hdr("a", fldTypeInt), + hdr("b", fldTypeInt), + ), + expRows: rows( + row(int64(6), int64(2)), + ), + compare: compareExactUnordered, + }, { sqls: sqls( "SELECT COUNT(*) + 10 - 11 * 2 AS count_rows FROM count_test",