slowbench
RetrievalInferenceEvalsAgentsFindingsSeriesBenchmarksArchive
Retrieval4 min765 words

The error said no such column, and the column was a fragment of a user's query

SQLite FTS5 "no such column" from a pasted HTML tag: parameter binding stopped SQL injection but not the query language underneath, which parsed the bound value a second time.

Contents · 6 sections
  1. 1What the user typed
  2. 2Three shapes, one cause
  3. 3Counting it honestly
  4. 4The fix
  5. 5Proving the check works
  6. 6What to take from it

A search endpoint returned 500 on four queries out of nine hundred and fifty real ones. The error on one of them:

no such column: 8

That reads like a schema problem. Something is asking for a column called 8, so look at the table definition, look at the migration, look at whatever builds the SELECT.

None of that is where it is. The 8 came from a user.

What the user typed

They had pasted a fragment of an HTML page into the search box. Somewhere in it:

<meta charset="UTF-8">

The search runs on SQLite's full-text extension. The term goes in as a bound parameter, which is what everyone is told to do, and the code wraps it in double quotes so that the whole thing is treated as one phrase rather than a bag of words.

user typed     charset="UTF-8"
code sent      "charset="UTF-8""

Binding a parameter protects the SQL statement. It does not protect the value from the engine that reads it next. The full-text extension takes its argument and parses it again, in its own query language, where a double quote opens and closes a phrase.

"charset="      a phrase
8               a bare token, read as a column reference
""              an empty phrase

Hence no such column: 8. The message is accurate. It is describing a column reference that the user's paste created and that nobody in the codebase wrote.

Three shapes, one cause

Five of the nine hundred and fifty queries contained a double quote. All five throw at the search layer. What they throw depends on where the quotes land.

quoteserror
oneunterminated string
twosyntax error near a token from the middle of the query
fourno such column

There is no rule about odd or even. Any double quote at all breaks the phrase, and the count and position only decide which way the wreckage lands. Two of the three messages look like parse errors, which is at least honest. The third looks like a schema bug and sends you to the wrong file.

Counting it honestly

Four became 500 at the endpoint. Five throw when the search layer is called directly. The fifth does not surface as an error, and why it does not was never traced; something upstream apparently drops that term or takes another path, but that is a guess and it has not been checked.

So the accurate statements are: four of nine hundred and fifty real queries returned 500, and all five quote-containing queries throw at the component level. The tempting summary, that a quote breaks search, is contradicted by one case out of five, and the reason that case is different is unknown.

That gap is small and it is the kind that quietly becomes a false rule if the two numbers get merged into one sentence.

The fix

escaped = term.replace('"', '""')

Doubling a quote is how you put a literal one inside a phrase. One line, no schema change, no migration.

Finding it took a day. Fixing it took a line. That ratio is the normal one for this class of bug, and it is the reason the error message mattered so much: two thirds of the day went to the word "column."

Proving the check works

After the fix, the natural move is to run the nine hundred and fifty queries, watch them all pass, and call it done.

That confirms the code and not the check. A test suite that has stopped exercising the failing path also passes.

So the fix was reverted on purpose and the same sweep run again:

with the fix       950 queries, all answered, 485 seconds
reverted           4 failures, no such column and a syntax error

Now the green result means something, because the red result was produced on demand from the same harness. A passing gate is only evidence if you have seen it fail.

What to take from it

Parameter binding solves exactly one problem, which is the SQL statement being rewritten by its own inputs. If the bound value is then handed to something with its own grammar, a full-text query language, a glob, a regular expression, a template, the input is unescaped again for that grammar and nobody mentioned it.

The general question is not whether input is parameterised. It is how many parsers the input passes through, and whether it has been escaped for each of them.