Skip to content

Fix IS NOT NULL parsing precedence with AND operator#109

Draft
Copilot wants to merge 2 commits intomasterfrom
copilot/fix-is-not-null-parsing-errors
Draft

Fix IS NOT NULL parsing precedence with AND operator#109
Copilot wants to merge 2 commits intomasterfrom
copilot/fix-is-not-null-parsing-errors

Conversation

Copy link
Contributor

Copilot AI commented Feb 14, 2026

The parser was tokenizing IS NOT NULL as two operations: IS NOT (binary) followed by NULL (literal), causing incorrect precedence when combined with logical operators.

Example of incorrect parsing:

-- Query
f IS NOT NULL AND v > 10

-- Was parsed as (incorrect)
f IS NOT (NULL AND (v > 10))

-- Should parse as
(f IS NOT NULL) AND (v > 10)

Changes:

  • Modified scanBinaryOp() to recognize the three-token sequence IS NOT NULL and return NOTNULL token before attempting to return ISNOT
  • This ensures IS NOT NULL is treated as a unary operator (like IS NULL) rather than a binary operator followed by a literal
  • Added test coverage for IS NOT NULL standalone and with AND operator

The fix aligns the parser behavior with SQLite's actual precedence rules for null checks.

Original prompt

This section details on the original issue you should resolve

<issue_title>IS NOT NULL parsing errors</issue_title>
<issue_description>The following query:

f IS NOT NULL AND v > 10

is being parsed as:

("f") IS NOT ((NULL) AND (("v") > (10)))

but it should be being parsed as:

("f" IS NOT NULL) AND ("v" > 10)

We can see this with the following snippet: https://go.dev/play/p/RI86BpGBz5s

package main

import (
	"fmt"
	"log"
	"strings"

	"github.com/rqlite/sql"
)

func main() {
	for _, q := range []string{
		`f IS NULL`,
		`f IS NULL AND v > 10`,
		`f IS NOT NULL`,
		`f IS NOT NULL AND v > 10`,
	} {
		expr, err := sql.NewParser(strings.NewReader(q)).ParseExpr()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%s %T\n\t%s\n", q, expr, brackets(expr))
	}
}

func brackets(e sql.Expr) sql.Expr {
	switch e := e.(type) {
	case *sql.UnaryExpr:
		e.X = &sql.ParenExpr{X: brackets(e.X)}
	case *sql.BinaryExpr:
		e.X = &sql.ParenExpr{X: brackets(e.X)}
		e.Y = &sql.ParenExpr{X: brackets(e.Y)}
	}
	return e
}

Produces the following output:

f IS NULL *sql.Null
	"f" IS NULL
f IS NULL AND v > 10 *sql.BinaryExpr
	("f" IS NULL) AND (("v") > (10))
f IS NOT NULL *sql.BinaryExpr
	("f") IS NOT (NULL)
f IS NOT NULL AND v > 10 *sql.BinaryExpr
	("f") IS NOT ((NULL) AND (("v") > (10)))

The IS NULL examples shown there are correct and are parsed as *sql.Null.

Confirming this behaviour with SQLite:

sqlite> CREATE TABLE fv (f, v);
sqlite> INSERT INTO TABLE fv VALUES (NULL, 0), (NULL, 20), (true, 0), (true, 20);
sqlite> SELECT * FROM fv;
+---+----+
| f | v  |
+---+----+
|   | 0  |
|   | 20 |
| 1 | 0  |
| 1 | 20 |
+---+----+

sqlite> SELECT * FROM fv WHERE f IS NOT NULL AND v > 10;
+---+----+
| f | v  |
+---+----+
| 1 | 20 |
+---+----+
sqlite> SELECT * FROM fv WHERE ("f") IS NOT ((NULL) AND (("v") > (10)));
+---+----+
| f | v  |
+---+----+
|   | 0  |
| 1 | 0  |
| 1 | 20 |
+---+----+

Thanks for your continued support with these issues @otoolep 🙏 </issue_description>

Comments on the Issue (you are @copilot in this section)

@otoolep Thanks for the report. Do you know if this is called out specifically in the syntax docs for SQLite?

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: otoolep <536312+otoolep@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix IS NOT NULL parsing errors in SQL Fix IS NOT NULL parsing precedence with AND operator Feb 14, 2026
Copilot AI requested a review from otoolep February 14, 2026 00:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IS NOT NULL parsing errors

2 participants