diff --git a/executor.go b/executor.go index 62ac7e7ff..0b922fedb 100644 --- a/executor.go +++ b/executor.go @@ -715,7 +715,30 @@ func (e *Executor) executeFieldRangeSlice(ctx context.Context, index string, c * fieldName, cond = k, vv } - if cond.Op == pql.BETWEEN { + // EQ null (not implemented: flip frag.FieldNotNull with max ColumnID) + // NEQ null frag.FieldNotNull() + // BETWEEN a,b(in) BETWEEN/frag.FieldRangeBetween() + // BETWEEN a,b(out) BETWEEN/frag.FieldNotNull() + // EQ frag.FieldRange + // NEQ (not implemented: frag.FieldRange) + + // Handle `!= null`. + if cond.Op == pql.NEQ && cond.Value == nil { + // Find field. + field := f.Field(fieldName) + if field == nil { + return nil, ErrFieldNotFound + } + + // Retrieve fragment. + frag := e.Holder.Fragment(index, frame, ViewFieldPrefix+fieldName, slice) + if frag == nil { + return NewBitmap(), nil + } + + return frag.FieldNotNull(field.BitDepth()) + + } else if cond.Op == pql.BETWEEN { predicates, err := cond.IntSliceValue() if err != nil { diff --git a/executor_test.go b/executor_test.go index 2a5b90e4b..83199f318 100644 --- a/executor_test.go +++ b/executor_test.go @@ -764,6 +764,14 @@ func TestExecutor_Execute_FieldRange(t *testing.T) { } }) + t.Run("NEQ", func(t *testing.T) { + if result, err := e.Execute(context.Background(), "i", test.MustParse(`Range(frame=other, foo != null)`), nil, nil); err != nil { + t.Fatal(err) + } else if !reflect.DeepEqual([]uint64{0}, result[0].(*pilosa.Bitmap).Bits()) { + t.Fatalf("unexpected result: %s", spew.Sdump(result)) + } + }) + t.Run("LT", func(t *testing.T) { if result, err := e.Execute(context.Background(), "i", test.MustParse(`Range(frame=f, foo < 20)`), nil, nil); err != nil { t.Fatal(err) diff --git a/pql/parser.go b/pql/parser.go index ab5298458..3af0cbc9c 100644 --- a/pql/parser.go +++ b/pql/parser.go @@ -165,7 +165,7 @@ func (p *Parser) parseArgs() (map[string]interface{}, error) { var op Token switch tok, pos, lit := p.scanIgnoreWhitespace(); tok { case ASSIGN: - case EQ, LT, LTE, GT, GTE, BETWEEN: + case EQ, NEQ, LT, LTE, GT, GTE, BETWEEN: op = tok default: return nil, parseErrorf(pos, "expected equals sign or comparison operator, found %q", lit) diff --git a/pql/parser_test.go b/pql/parser_test.go index 266e1de1f..0e2a5c17d 100644 --- a/pql/parser_test.go +++ b/pql/parser_test.go @@ -172,7 +172,7 @@ func TestParser_Parse(t *testing.T) { // Parse with condition arguments. t.Run("WithCondition", func(t *testing.T) { - q, err := pql.ParseString(`MyCall(key=foo, x == 12.25, y >= 100, z >< [4,8])`) + q, err := pql.ParseString(`MyCall(key=foo, x == 12.25, y >= 100, z >< [4,8], m != null)`) if err != nil { t.Fatal(err) } else if !reflect.DeepEqual(q.Calls[0], @@ -183,6 +183,7 @@ func TestParser_Parse(t *testing.T) { "x": &pql.Condition{Op: pql.EQ, Value: 12.25}, "y": &pql.Condition{Op: pql.GTE, Value: int64(100)}, "z": &pql.Condition{Op: pql.BETWEEN, Value: []interface{}{int64(4), int64(8)}}, + "m": &pql.Condition{Op: pql.NEQ, Value: nil}, }, }, ) { diff --git a/pql/scanner.go b/pql/scanner.go index f25abf1f4..5a24b6af2 100644 --- a/pql/scanner.go +++ b/pql/scanner.go @@ -66,6 +66,12 @@ func (s *Scanner) Scan() (tok Token, pos Pos, lit string) { } s.unread() return ASSIGN, pos, string(ch) + case '!': + if next := s.read(); next == '=' { + return NEQ, pos, "!=" + } + s.unread() + return ASSIGN, pos, string(ch) case '<': if next := s.read(); next == '=' { return LTE, pos, "<=" diff --git a/pql/scanner_test.go b/pql/scanner_test.go index 98c13e232..e48896748 100644 --- a/pql/scanner_test.go +++ b/pql/scanner_test.go @@ -38,6 +38,7 @@ func TestScanner_Scan(t *testing.T) { {name: "ASSIGN", s: `=`, tok: pql.ASSIGN, lit: `=`}, {name: "EQ", s: `==`, tok: pql.EQ, lit: `==`}, + {name: "NEQ", s: `!=`, tok: pql.NEQ, lit: `!=`}, {name: "LT", s: `<`, tok: pql.LT, lit: `<`}, {name: "LTE", s: `<=`, tok: pql.LTE, lit: `<=`}, {name: "GT", s: `>`, tok: pql.GT, lit: `>`}, diff --git a/pql/token.go b/pql/token.go index 2870553e4..6997f17af 100644 --- a/pql/token.go +++ b/pql/token.go @@ -39,6 +39,7 @@ const ( ASSIGN // = EQ // == + NEQ // != LT // < LTE // <= GT // > @@ -64,6 +65,7 @@ var tokens = [...]string{ ASSIGN: "=", EQ: "==", + NEQ: "!=", LT: "<", LTE: "<=", GT: ">",