All rules Rule SL028 · IX
SQLite · beta
critical
IX · Index hygiene
free in the CLI, --engine=sqlite

Why does SQLite refuse an index on a non-deterministic expression?

Index on a non-deterministic expression

Critical: SQLite refuses the statement, so the migration fails here.

What happens

An index expression must give the same answer for the same row every time, so SQLite refuses random(), changes(), last_insert_rowid() and their kin in CREATE INDEX: "non-deterministic functions prohibited in index expressions". The statement fails and the migration stops.

Why it is dangerous on a populated table

SQLite holds one write lock for the whole database, so anything that rebuilds a table blocks every writer for as long as the copy takes, and that time grows with the table.

Fires on

CREATE INDEX orders_shuffle ON orders (id + random());

The safe pattern

Index a deterministic expression, lower(), a column arithmetic, a JSON path, or store the value in a column and index that.

CREATE INDEX orders_email_ci ON orders (lower(email));

Fixtures

The rule ships with these files and the test suite runs them on every change: the first set must fire, the second must stay silent.

Fires (2)

changes
CREATE INDEX orders_changes ON orders (changes());
random
CREATE INDEX orders_shuffle ON orders (id + random());

Stays silent (2)

lower
CREATE INDEX orders_email_ci ON orders (lower(email));
plain column
CREATE INDEX orders_status ON orders (status);

How to check locally

Catch this before it ships

SQLite support is in beta: this rule runs locally in the free CLI, static only, and not in the hosted service yet. No install, nothing leaves your machine:

npx bolvrk check migration.sql --engine=sqlite