Skip to content

Partial index causes rows with NULL to be invisible in unrelated multi-column WHERE queries #2227

@masanork

Description

@masanork

Description

When a partial index with WHERE column IS NOT NULL exists on a table, rows where that column is NULL become invisible to multi-column WHERE queries — even when the query does not reference the NULL column at all.

Reproduction

CREATE TABLE test_table (
    id         TEXT PRIMARY KEY,
    task_id    TEXT NOT NULL,
    status     TEXT NOT NULL,
    deadline   INTEGER          -- nullable
);

-- This partial index triggers the bug
CREATE INDEX idx_status_deadline
    ON test_table(status, deadline)
    WHERE deadline IS NOT NULL;

CREATE INDEX idx_task_id
    ON test_table(task_id);

-- Insert a row with deadline = NULL
INSERT INTO test_table (id, task_id, status, deadline)
VALUES ('row-1', 'task-A', 'active', NULL);

Expected

SELECT count(*) FROM test_table WHERE task_id = 'task-A';              -- 1 ✓
SELECT count(*) FROM test_table WHERE status = 'active';               -- 1 ✓
SELECT count(*) FROM test_table WHERE task_id = 'task-A' AND status = 'active';  -- 1 (expected)

Actual

SELECT count(*) FROM test_table WHERE task_id = 'task-A';              -- 1 ✓
SELECT count(*) FROM test_table WHERE status = 'active';               -- 1 ✓
SELECT count(*) FROM test_table WHERE task_id = 'task-A' AND status = 'active';  -- 0 ✗ BUG

Each column matches individually (count = 1), but the combined AND returns 0. The UPDATE ... WHERE task_id = ? AND status = ? also affects 0 rows.

Additional observations

  • Rows inserted without NULL in the indexed column are not affected
  • The bug occurs with raw SQL literals (no parameter binding involved)
  • Named parameters and positional parameters both reproduce the same behavior
  • UPDATE ... SET status = 'completed' (unconditional, no WHERE) succeeds and affects 1 row — confirming the row exists
  • Dropping the partial index immediately fixes the issue; replacing with a regular composite index (no WHERE clause) also works

Workaround

Replace the partial index with a regular composite index:

DROP INDEX IF EXISTS idx_status_deadline;
CREATE INDEX idx_status_deadline ON test_table(status, deadline);  -- no WHERE clause

Environment

  • turso crate: 0.6.0-pre.15 (Rust SDK)
  • Platform: macOS (Darwin 25.4.0), aarch64
  • Rust: 1.94.0
  • Storage: in-memory (:memory:) — reproducible without file-based DB

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions