Equivl

Check whether two SQL queries are equivalent

A SQL equivalence check runs two queries against the same generated tables and reports the first set of tables where their results differ, shrunk to the fewest rows that still show the difference. It runs SQLite, so it answers for SQLite.

Compare two snippets

JavaScript, TypeScript and Python run in your browser. Nothing to install, no account.

How it works

In Code mode, pick SQL (SQLite) from either language menu. Both sides become query editors, and a Schema editor appears above them for the CREATE TABLE statements the two queries share. Equivl never parses the schema itself: SQLite, compiled to WebAssembly and running in a Web Worker in your browser, executes it, and the row generator reads the tables, column types, primary keys, UNIQUE indexes, NOT NULLs and foreign keys back from SQLite. Nothing is uploaded.

Each of the 1,000 cases fills every table with up to 5 rows. Values come from a handful of small numbers and short strings plus the literals written in either query, and a number n in a query also brings n - 1 and n + 1, which is how a boundary such as total > 100 against total >= 100 is found. About one nullable cell in five is NULL, every foreign key points at a real row, and the first case has every table empty. Both queries run on the same rows, which are rolled back before the next case. A seed makes every run reproducible.

On five pairs chosen to differ in these ways (a LEFT JOIN rewritten as an INNER JOIN, NOT IN against NOT EXISTS when a key is NULL, COUNT(*) against COUNT(column), > against >=, and = against >= on a bind parameter), the difference was found on each of 50 seeds, never later than the 25th case. 1,000 cases take a fraction of a second.

Android Room queries

A Room @Query can be pasted as written. Kotlin templates such as ${Employee.TABLE} and $TABLE_NAME are read as the table or column they name, the same way in the schema and in both queries. Equivl takes the value from a const val you paste into the Room constants field, or from the same template written in the schema, or by matching the constant's name to exactly one table or column in the schema. Every template it read is listed under the editors, and one it cannot place is an error that names it, never a guess. A whole @Query("…") annotation, and Java's "SELECT * FROM " + Employee.TABLE concatenation, are unwrapped. Bind parameters such as :id get a generated value in each case, the same value for both queries.

What is compared

What you see

Limits

Questions

Can Equivl prove two SQL queries are equivalent?

No. It runs them on 1,000 generated sets of tables by default and shows the smallest tables they disagree on if it finds one. No difference found is evidence, not proof: a difference that needs larger tables or other values can still go unfound.

Which database does it use?

SQLite, compiled to WebAssembly and running in your browser. Queries written for PostgreSQL, MySQL or SQL Server may behave differently under it.

Can I paste an Android Room query with ${} constants?

Yes. Kotlin templates like ${Employee.TABLE} are read as the tables and columns they name, from pasted const vals, the schema, or a unique name match, and each reading is listed. A whole @Query annotation or Java string concatenation is unwrapped, and :name parameters get generated values.

Are my schema and queries uploaded?

No. SQLite runs in a Web Worker in your browser. A share link carries the schema, both queries and the settings in the part of the URL after the #, which browsers do not send to a server.

Related