Skip to content

Answer an Android database the way it is actually shaped - #29

Merged
rahulmahadik merged 3 commits into
mainfrom
develop
Aug 17, 2026
Merged

Answer an Android database the way it is actually shaped#29
rahulmahadik merged 3 commits into
mainfrom
develop

Conversation

@rahulmahadik

Copy link
Copy Markdown
Owner

Three commits: the engine and connector work, the JetBrains plugin, and the version bumps.

Why

JetBrains installs are dominated by Android Studio, so the database this plugin opens is usually SQLite via Room. Every fixture the suite owned used real types - Chinook's InvoiceDate is a DATETIME - so nothing had ever exercised that shape: dates as INTEGER epoch milliseconds, booleans as 0/1, ids as Longs past what a double holds, BLOB columns, and Room's bookkeeping and FTS shadow tables beside the app's own. The first time such a database was tried it produced six defects, none of which raised an error.

Fault What the user saw
Epoch date comparison "How many users signed up in the last 7 days" answered 0, or the whole table
BLOB column Every row of the query lost, reported as "the database didn't accept that query"
rowid Refused as an invented column after every correction attempt
A file that is not a database ~21,000 reopens a second until timeout, then an unrelated message
WAL database without its -wal Reported as empty; every question answered "no such table"
MATCH Every Room @Fts4 query refused as unparseable

The date one is the worst: a 7B model answered 0 where the truth was 2, and a 30B model answered 5 - a larger model failing in the opposite direction. Both answer 2 now.

Versions

Package Version
@asksql/core 0.8.0
@asksql/sqlite 0.5.0
JetBrains plugin 0.5.4

VS Code stays at 0.7.2 and the browser extension at 0.3.1; neither has a source change.

Behaviour a user can see change

  • SQLite MATCH is accepted, so full-text search runs. Writes, stacked statements, denied functions and a non-literal right side are still refused.
  • SELECT rowid is accepted on SQLite.
  • An integer timestamp column carries its unit in the schema the model reads, decided from an aggregate, so no cell value reaches the model.

Verification

Check Result
TypeScript 3013 passed
Kotlin unit 749 passed
Kotlin integration 112 passed, 0 skipped
Real databases, 6 engines 18/18
Graduated matrix, easy to hard 70/70
Room-shaped harness (pnpm test:room) 12/12
Packaged consumer, pnpm verify passed, exit 0

The two new checks are held to roughly 67,000 generated statements across both surfaces, in both directions - a rule that fires on a TEXT column would refuse SQL that is correct.

Not verified: no real device database has been opened, and CI runs on Ubuntu only while Android Studio skews Windows.

Every fixture the suite owned used real types - Chinook's InvoiceDate is a DATETIME - so nothing ever
exercised the shape a Room database has: a date is an INTEGER of epoch milliseconds, a boolean is 0 or
1, an id is a Long past what a double holds, and the app's tables sit beside Room's own bookkeeping and
an FTS table's shadow tables. The first time such a database was tried, it answered questions wrongly
without erring once.

A date comparison was the worst of it. The SQLite guidance said to use date('now','-30 days'), which is
right for a TEXT column and wrong for an INTEGER one, and SQLite compares by storage class, so nothing
matches, nothing errors, and "how many users signed up in the last 7 days" answers zero. Guessing epoch
seconds instead is worse: a milliseconds column is a thousand times larger, so every row matches and the
answer is the whole table. A 7B model answered 0 where the truth was 2; a 30B model answered 5. The
guidance now states which units a column is in, a semantic floor catches a numeric column compared
against a date, and the schema carries the unit itself, decided from an aggregate so no value is read
into the prompt. Both models now answer 2.

Full-text search runs. SQLite parses under the Postgresql grammar, which has no MATCH, so every query a
Room @fts4 entity is queried with was refused as unparseable. MATCH is validated as a comparison and the
statement that runs keeps it verbatim; writes, stacked statements, denied functions and a non-literal
right side are all still refused. Verified on FTS4 and FTS5.

rowid is no longer called an invented column: SQLite gives every table rowid, oid and _rowid_ without
listing them, and FTS tables answer to docid and rank. On a WITHOUT ROWID table the database rejects the
name, which the correction loop can act on.

A database copied without its -wal file said it was empty. Room defaults to WAL, so pulling app.db off a
device and leaving the sidecars behind left SQLite reporting no tables and every question answering "no
such table". It now says which file is missing. The header and the sidecar are read before the database
is opened, because SQLite creates an empty -wal itself as soon as it is.

tools/room-regression.mjs runs the engine against that shape with no model involved, so it gates CI.
Android Studio is where this plugin is mostly installed, so the database it opens is usually SQLite via
Room. Four faults sat in that path, none of them reachable by a fixture built on ordinary types.

A query over a table with a ByteArray column returned nothing at all. The driver reports those cells as
BLOB and implements none of the streaming interface, so reading them threw and the whole result set went
with it, reported to the user as "the database didn't accept that query". The bytes read back fine the
other way.

Opening a file that is not a readable database reopened it about twenty-one thousand times a second
until the operation timed out, pegging a core and then reporting something unrelated. The driver signals
that case by throwing where it is documented to return false, which read as "stale connection, try
again". It now fails once and says that a database pulled from a device needs its -wal and -shm files
alongside it - which is also how the sidecar gets picked by mistake in the first place.

SELECT rowid was refused as an invented column, and a date comparison against an epoch-milliseconds
column answered zero or everything without erring, the same fault fixed in the engine. Full-text search
over a Room @fts4 entity ran at all: MATCH is not in the SQL parser's grammar, so every such query had
been refused as unparseable.

A relationship question about two collections is now answered in prose on MongoDB, as it already was on
the SQL engines, rather than returning documents.
Both minor: the engine gains a semantic floor for a numeric column compared against a date, accepts
SQLite's MATCH, and recognises the columns SQLite gives every table without listing them; the connector
states which units an integer timestamp is in and says when a database was copied without its -wal file.

The lockfile moves with them: it records the workspace versions, so leaving it behind fails the frozen
install CI runs.
@rahulmahadik
rahulmahadik merged commit b75a3cd into main Aug 17, 2026
23 checks passed
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.

1 participant