SQLite Fixed a Bug. Then It Put It Back.

4 min read

I came across something in SQLite that I found way funnier than I should have.

For years, some of its JSON functions accepted a kind of input they weren't supposed to accept.

The documentation said the input should fail.

SQLite accepted it anyway.

Nobody seems to have deliberately designed this behaviour. SQLite's own documentation describes it as a bug.

Then, in January 2024, the developers rewrote part of the JSON implementation and accidentally fixed it.

Great.

Except applications broke.

Some software had been relying on the supposedly incorrect behaviour for years. So about two weeks later, SQLite brought it back.

This time on purpose.

The old behaviour is now documented and officially supported for backwards compatibility. SQLite actually calls this bug-compatibility.

I love this.

They had a bug. They fixed it. The fix broke things. So they put the bug back.

It wasn't even that weird a thing to depend on

This detail made the story better.

SQLite's command-line tool has a function called readfile() that reads a file and hands it back as a BLOB, basically raw bytes without claiming those bytes are text.

So if data.json contained some JSON, something as ordinary as:

SELECT readfile('data.json') -> 1;

just worked.

It wasn't supposed to. The documentation said SQLite's JSON functions shouldn't accept BLOB input like that. The old implementation accepted it anyway when the bytes happened to contain valid JSON text.

SQLite's own documentation now acknowledges that readfile() helped people end up depending on the bug. It handed them JSON files as BLOBs, and the JSON functions had happily accepted them for years.

The part I liked even more was Richard Hipp's reaction when the breakage came up.

He said the developers hadn't known about the bug. If he had known, he probably would have tried to preserve the buggy behaviour deliberately for compatibility.

Even the person maintaining the thing ended up in roughly the same weird place:

okay, it's a bug... but people use the bug now.

So... what was correct anymore?

Before the fix, the situation seems easy enough:

documentation:
this input should fail

software:
nah, I'll accept it

Bug.

Then SQLite changed the implementation so the software finally behaved the way the documentation said it should.

Which sounds like the correct thing to do.

Except now:

documentation:
correct

implementation:
correct

existing applications:
broken

That's where I got stuck.

The old behaviour didn't suddenly become technically correct because somebody depended on it. SQLite still calls it a bug.

But once other software had been built around that behaviour, fixing the original mistake was no longer only about the original mistake.

There was other stuff attached to it now.

Apparently there is a name for this problem

I eventually ran into Hyrum's Law.

The rough idea is that once an API has enough users, somebody will eventually depend on pretty much every observable behaviour it has.

Not just what the documentation promises.

The stuff it happens to do too.

That made the SQLite story click for me.

I had been thinking of a software contract mostly as:

here's what the documentation says the software will do.

But users don't interact only with documentation.

They interact with the actual software.

So there can be a gap between:

what the developers intended

what the documentation said

what the software actually did

In SQLite's case, the first two agreed.

The third didn't.

Unfortunately, the third was also the one applications had been talking to for years.

So when SQLite finally made all three agree, some of those applications basically went:

yeah, no.

The computer didn't care that its dependency had started life as an accident.

It just broke.

SQLite isn't the only software carrying old mistakes around. JavaScript still gives you typeof null === "object" because changing the old behaviour would break compatibility, and Excel still treats 1900 as a leap year because correcting the inherited Lotus 1-2-3 behaviour would disturb existing dates and formulas.

Those are basically software trivia at this point.

The SQLite one interested me more because you can watch the whole thing happen: bug, fix, breakage, bug restored, bug officially supported.

The neat explanation doesn't quite work either

For a minute I thought:

once people depend on a bug, it becomes part of the contract.

Nice.

Except that can't be the whole rule.

Security was the first thing that made the neat version fall apart for me. Surely there are cases where preserving old behaviour just because something depends on it would be a terrible idea.

So "people depend on it" isn't enough.

But neither is:

it's a bug, so fix it.

SQLite did that.

Things broke.

There are obviously ways software projects deal with this stuff. Versions, deprecations, migration periods, compatibility modes, and probably a lot of other machinery I'm only beginning to understand.

But I don't think I need a complete theory of API evolution to find the original situation strange.

I think this is what amused me

typeof null is still weird. February 29, 1900 still didn't happen. SQLite's JSON functions were still doing something their own documentation said they shouldn't.

What changed was everything around the mistake.

Other code started assuming it. Workarounds appeared. Applications were built while the behaviour quietly sat underneath them.

Then somebody eventually comes along and says:

wait, this is wrong. I'll fix it.

And now that is the thing causing trouble.

I started with a SQLite changelog and somehow ended up wondering whether software can behave incorrectly for long enough that correcting it becomes the more disruptive choice.

Apparently it can.

Not because the mistake became right.

It just stopped being alone.

You might also like:

Stay in touch

Occasional reflections on mindfulness and intentional living.