Formal verification for SQLite
(1) By Guillaume Claret (clarus) on 2025-12-18 12:56:19 [link] [source]
Hi,
There was a recent post on Hacker News about the extensive test suite for SQLite https://news.ycombinator.com/item?id=46303277
Something I have always wondered is how effective formal verification would be on a database system like SQLite. Given that the test suite is very complete and that the cost of formal verification is going down, it might even be economically relevant.
If done correctly, it would really mean there are no bugs, apart from system outside of SQLite itself (like the C compiler, OS primitives, ...). Optimizations could be more aggressive; for example, this is one of the main reasons AWS claims to use formal methods.
Is that something you have been looking at and think is worth considering?
"Formal methods" is a name grouping many different techniques, from static analysis to full theorem proving. Only the latter would be effective to fully verify a codebase like SQLite.
(2) By Richard Hipp (drh) on 2025-12-18 13:13:03 in reply to 1 [link] [source]
Formal verification is a technique for ensuring that all bugs in the design specification are faithfully reproduced in the implementation. Formal verification does not prevent bugs. It merely shifts the source of the bugs from the implementation back to the design specification.
(3) By Guillaume Claret (clarus) on 2025-12-18 13:55:19 in reply to 2 [link] [source]
Yes, but the "design specification" is much simpler than the code, especially for an optimized C program like SQLite. There, the design specification would be the naive implementation of SQL without caring at all about algorithmic complexity, in a functional language.
There is also a well-tested design specification, which is the current SQLite version, on which further optimizations could be applied while being quite sure nothing is breaking.
I can also quote the CompCert formally verified C compiler, which was the only compiler found without bugs after an extensive fuzzing with GCC, Clang, and other popular compilers, which all got subtle issues.
(4) By Richard Hipp (drh) on 2025-12-18 14:11:02 in reply to 3 [link] [source]
Yes, but the "design specification" is much simpler than the code, especially for an optimized C program like SQLite.
Mythinks you have never actually tried to write a formal design specification for something like SQLite. It turns out to be rather more complex than you might imagine. I invite you to try it and see for yourself.
I have tried to write a formal design spec for SQLite. I spent years working on it, but it never got anywhere close to being complete. And after all that time and effort, I never found it to add any value to the project, so I eventually abandoned the effort.
Maybe you can do better. Maybe you know some tricks for writing formal design specifications that I don't, and that make the process easier. Try it yourself. Prove me wrong.
(5) By Guillaume Claret (clarus) on 2025-12-18 14:18:07 in reply to 4 [link] [source]
OK, thanks for your answer, we indeed need to try, we will try!
(9) By Guillaume Claret (clarus) on 2026-04-26 14:54:09 in reply to 5 [link] [source]
Hi,
As an update, we made experiments around the formal verification of the SQLite code, using the approach of translation with AI of the C code to the formal language Rocq, generating equivalence tests.
This is only the first step of a full formal verification, which would additionally require proving the equivalence of the translation to the original C code and verifying properties about this translation.
We conclude that this is still a big project to run with current technologies. We still made the translation of a substantial part of the B-tree implementation to Rocq, with corresponding tests. Results are in https://github.com/formal-land/rocq-of-db repository. We added many conventions and tests to make the translation with LLMs more stable, but even with that, it is starting to be too unstable and uses too many tokens, so we are switching to a simpler project.
We are happy to continue working on it when having more availability. Best!
(10) By Spindrift (spindrift) on 2026-04-27 05:50:41 in reply to 9 [link] [source]
Thank you for feeding back. It's interesting to see where the currently state of such technological tools is - presumably in X years time the answer may be different.
(11) By Pavan Nambi (Pavan-Nambi) on 2026-04-27 06:38:57 in reply to 10 [link] [source]
Hi lol thanks for whoever bumped this thread.
i was learning how to use formal methods - not for sqlite but for turso. but since sqlite is our defacto standard and is obviously more reliable and have better docs, i picked sqlite-c-api to try and i agree it takes sooo long but quint(the tool i was using to do this) found a counter example.
for this case
package require sqlite3
file delete -force tcltestingcapi.db tcltestingcapi.db-journal tcltestingcapi.db-wal
sqlite3 db tcltestingcapi.db
db eval {CREATE TABLE t(x); INSERT INTO t VALUES(1),(2);}
set seen 0
set blob [db serialize main]
db eval {SELECT x FROM t} {
incr seen
if {$seen == 1} {
db deserialize main $blob
}
}
db close
c file repro: https://gist.github.com/Pavan-Nambi/83f04a6f1d189a58adf98bde73257c59
on mac os both are seg fault crashes
on linux... sometimes it crashing sometimes its not and on valgrind it says invalid reads from freed memory, SQLITE_CORRUPT (database disk image is malformed)
i haven't made any report regarding this cuz.. most of the time i am second guessing myself thinking if i am doing something wrong or something i am not allowed to. but seeing this thread pop up gave me little courage..
SQLite docs say sqlite3_deserialize() fails with SQLITE_BUSY if the database is currently in a read transaction
i could push my repo to github - when i get back home in 5-6 hours. if anyone is interested.
sorry i know this message might sound very unclear and all over the place. i am typing from mobile and not near my pc/home, was just excited when i saw this discussion.... again i could very well be doing something wrong/invalid usage. if that's the case please let me know, thanks
Also yes - I could be wrong here, I could be doing something stupid. I'm not expert and still learning things, so I appreciate any feedback, thanks
(12) By Pavan Nambi (Pavan-Nambi) on 2026-04-27 12:45:03 in reply to 11 [link] [source]
thanks, this seems to be fixed in trunk now https://github.com/sqlite/sqlite/commit/1fc7341ad2331b31ba5edc0160554ce4a1bef35e
for anyone interested in gh repo here - https://github.com/Pavan-Nambi/sqlite-c-api-quint
please note that there might some inconsistencies - it is part of my formal-methods-learning repo i keep locally i only pushed relevant things so some stuff in there might reference things u don't see on github.
(13) By Stephan Beal (stephan) on 2026-04-27 12:46:44 in reply to 11 [link] [source]
SQLite docs say sqlite3_deserialize() fails with SQLITE_BUSY if the database is currently in a read transaction
Thanks to your post including easy reproductions, this was quickly fixed: src:77662cce9a
(14.1) By Pavan Nambi (Pavan-Nambi) on 2026-04-29 12:00:49 edited from 14.0 in reply to 13 [source]
Hi, my tests are failing on this now and yes this looks like a api mis-use to me yes, but i want to ask if crash here is intended cuz it appears to be crashed as deserialise isn't failing with busy...? thanks.
#include <sqlite3.h>
#include <stdio.h>
#include <string.h>
int main(void){
sqlite3 *src = 0, *dst = 0;
sqlite3_open("/tmp/repro_min_src.db", &src);
sqlite3_open("/tmp/repro_min_dst.db", &dst);
sqlite3_int64 n = 0;
unsigned char *img = sqlite3_serialize(dst, "main", &n, 0);
if( !img ) return 2;
unsigned char *p = sqlite3_malloc64((sqlite3_uint64)n);
if( !p ) return 3;
memcpy(p, img, (size_t)n);
sqlite3_backup *b = sqlite3_backup_init(dst, "main", src, "main");
if( !b ) return 4;
int d = sqlite3_deserialize(
dst, "main", p, n, n,
SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE
);
fprintf(stderr, "deserialize=%d(%s)\n", d, sqlite3_errstr(d));
int s = sqlite3_backup_step(b, 0);
int f = sqlite3_backup_finish(b);
fprintf(stderr, "step=%d(%s) finish=%d(%s)\n",
s, sqlite3_errstr(s), f, sqlite3_errstr(f));
sqlite3_free(img);
sqlite3_close(src);
sqlite3_close(dst);
return 0;
}
(15) By Stephan Beal (stephan) on 2026-04-30 13:57:22 in reply to 14.1 [link] [source]
my tests are failing on this now...
Please try out src:1f940357f7 and let us know if it's still failing.
(16) By Pavan Nambi (Pavan-Nambi) on 2026-04-30 17:35:24 in reply to 15 [link] [source]
hi, thanks, i no longer have any crashes around deserialise, i am trying to add more funcs
as of now this is where it fails:
sqlite3 src :memory:
src eval {
CREATE TABLE t(x);
INSERT INTO t VALUES(1);
}
set blob [src serialize main]
sqlite3 dst :memory:
dst deserialize -readonly 1 $blob
set ro [sqlite3_db_readonly dst main]
set rc [catch {dst eval {INSERT INTO t VALUES(2)}} msg]
puts "db_readonly=$ro"
puts "write_catch_rc=$rc"
puts "write_msg=$msg"
Observed:
db_readonly=0
write_catch_rc=1
write_msg=attempt to write a readonly database
in specs i was expecting db_readonly to be 1
sqlite docs say
The sqlite3_db_readonly(D,N) interface returns 1 if the database N of connection D is read-only, 0 if it is read/write, or -1 if N is not the name of a database on connection D.
second issue is: this looks like most recent change..
for {set i 0} {$i < 200000} {incr i} {
alloc_dealloc_mutex
}
puts DONE
sometimes i am getting undefined behaviour , sometimes no,
sqlite3.c:30828:32: runtime error: member access within misaligned address 0x000106358e90 for type 'sqlite3_mutex' (aka 'struct sqlite3_mutex'), which requires 128 byte alignment
0x000106358e90: note: pointer points here
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
^
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior sqlite3.c:30828:32
(17) By Pavan Nambi (Pavan-Nambi) on 2026-04-30 17:46:00 in reply to 16 [link] [source]
also another issue i just want to ask what's the expected output here.... (i'm likely writing specs wrong to run into this lol) or if this should give api misuse
https://gist.github.com/Pavan-Nambi/e2db9f211d5607876d1caa2c4071ead1
currently this gives
backup_step rc=0
deserialize rc=11 (SQLITE_CORRUPT)
errcode=11 ex=11 msg=malformed database schema (x) - invalid rootpage
(18) By Stephan Beal (stephan) on 2026-04-30 18:45:52 in reply to 16 [link] [source]
sqlite3.c:30828:32:
Without knowing the very specific version you're using, line numbers are not terribly useful. Please always provide a version hash (preferably the one from the canonical source tree rather than github, as that saves us having to back-track the github hash). The simplest way to get that in your case is:
$ grep 'define SQLITE_SOURCE_ID' sqlite3.c
The mutexes were recently re-packed to use 128-byte alignment and you may have discovered a regression involving that.
i'll need to ask Dan or Richard to address your other points.
PS: at this point we have thoroughly threadjacked someone else's largely-unrelated thread. Please post future reports in new threads.
(6) By jchd (jchd18) on 2025-12-18 15:05:10 in reply to 4 [link] [source]
Writing formal specifications isn't easy indeed, but it's the basis for building really proven-correct code.
The root of SQL and how it's supposed to work is already based on mathematics, so this part is not the most complex to express formally (mathematically).
In my experience, the best formal method for "assigning programs to meanings" is the B method. Starting form abstract formal specifications, from there successive steps of refinment (with obligatory proofs) make the model closer and closer to what is aimed at the low level (code) while insuring mathematical correctness at every step. Then a final step is converting the final B model into B0, ready for compiling in Ada, C, C++ by certified compilers. That is production of formally proven-correct programs.
This is the opposite approach of the OP, where one first write C (say) code (with potential bugs), then use tools to convert that into Coq (or its friends), then brainstorm on what are the supposed invariants and specifications meant at that level, etc. That is formal verification.
(7) By ralf (ralfbertling) on 2025-12-19 12:21:25 in reply to 2 [link] [source]
Hi,
I do get your point and agree it dows not prevent all bugs.
OTOH if you have a fresh approach (a formal specification or another implementation) for any difference you encounter, you can check if one or the other is wrong/a bug. Sometimes this might result in a clarification of some edge cases where the behaviour is currently unspecified, sometimes it will show errors in the fresh approach, sometimes it will discover problems in the current implementation.
Unless someone blindly repeats the same mistakes or blindly faithfully reproduces specification bugs to the shipping software, the odds would be that some bugs would be found/prevented from having an impact.
Regards, ralf
(8) By Simon Slavin (slavin) on 2025-12-19 18:50:25 in reply to 1 [link] [source]
I would argue that the design specification for SQLite is its documentation, available at https://sqlite.org.
Would you like to try formally verifying SQLite from that ? It would take thousands of hours. And do you think you could do a better job than the existing test suite ?
Also, what platforms would you verify it for ? SQLite is supposed to work with all C compilers, on a wide variety of platforms. Just because it works with one combination, doesn't mean it works for others. How would you finance the collection and maintenance of all the possible platforms SQLite might be used on ?