SQLITE_NOTFOUND occurs when code calls sqlite3_file_control() with an opcode that the underlying VFS (Virtual File System) implementation does not recognize or support. This typically happens with custom VFS implementations, version mismatches, or calling opcodes meant for internal use only.
The sqlite3_file_control() function is a low-level interface that allows direct communication with SQLite's Virtual File System (VFS) layer. It takes an opcode parameter that tells the VFS what operation to perform. When you pass an opcode (an integer code) that the VFS doesn't understand, it returns SQLITE_NOTFOUND (error code 12). Each VFS implementation only recognizes certain opcodes. SQLite core reserves opcodes less than 100 for its own use. Custom applications can define opcodes greater than 100. If a VFS encounters an opcode it doesn't implement, it must return SQLITE_NOTFOUND. This is the expected behavior for unrecognized or unsupported file control operations.
First, identify which opcode your code is using. Add logging to capture the exact opcode value:
int opcode = SQLITE_FCNTL_SIZE_HINT; // or whichever opcode you are using
int result = sqlite3_file_control(db, "main", opcode, &arg);
if (result == SQLITE_NOTFOUND) {
fprintf(stderr, "Opcode %d not supported by VFS\n", opcode);
}Cross-reference the opcode number against the official SQLite documentation to ensure it is valid and supported in your SQLite version.
Different SQLite versions introduce new opcodes over time. Check your SQLite version and the opcode documentation to confirm the opcode was available at that version.
const char *version = sqlite3_libversion();
fprintf(stderr, "SQLite version: %s\n", version);Upgrade SQLite if you are using a version that does not include the required opcode. Visit https://sqlite.org/download.html to download the latest version.
Some opcodes like SQLITE_FCNTL_SYNC and SQLITE_FCNTL_COMMIT_PHASETWO are generated internally by SQLite and should never be called directly from application code. Do not call sqlite3_file_control() with these opcodes:
- SQLITE_FCNTL_SYNC (internal)
- SQLITE_FCNTL_COMMIT_PHASETWO (internal)
- SQLITE_FCNTL_LOCKSTATE (debug only, requires SQLITE_DEBUG)
If your code is calling these, remove those calls.
If you are using a custom VFS (for example, RBU, ZipVFS, or a custom in-memory VFS), verify that it implements all opcodes you are trying to use.
For example, the RBU (Resumable Bulk Update) extension only implements SQLITE_FCNTL_RBU. All other standard opcodes may not be available. Check the documentation for your VFS:
- Official VFS list: https://sqlite.org/c3ref/c_fcntl_begin_atomic_write.html
- If using RBU: https://sqlite.org/rbu.html
- If using ZipVFS: consult ZipVFS documentation
Either upgrade your VFS to support the opcode, or switch to a VFS that does.
Some opcodes may be unsupported by your VFS because they are optional or not applicable. For common file control needs:
- Chunk size: Use SQLITE_FCNTL_CHUNK_SIZE (opcode 6) to optimize disk allocation.
- WAL persistence: Use SQLITE_FCNTL_PERSIST_WAL (opcode 10) only if your VFS supports it.
- File pointer access: Use SQLITE_FCNTL_FILE_POINTER (opcode 5) or SQLITE_FCNTL_JOURNAL_POINTER (opcode 12).
If an opcode is not supported, the VFS will correctly return SQLITE_NOTFOUND, and your application should handle this gracefully by either:
- Using a default behavior instead
- Trying a different opcode
- Logging a warning and continuing
int result = sqlite3_file_control(db, "main", opcode, &arg);
if (result == SQLITE_NOTFOUND) {
// Handle gracefully: opcode not supported
// Use alternative approach or log warning
fprintf(stderr, "Warning: opcode not supported\n");
}If you are using a custom VFS, temporarily switch to the default VFS to see if the error persists. This helps isolate whether the issue is with the VFS implementation or your opcode usage.
// Use default VFS
sqlite3_open_v2(filename, &db, flags, NULL);If the error goes away with the default VFS, the issue is with your custom VFS implementation. If the error persists, the opcode itself is unsupported or invalid.
SQLite's VFS system is extensible, but not all VFS implementations support all opcodes. The design is intentional: unsupported opcodes should return SQLITE_NOTFOUND rather than fail silently or corrupt data.
Custom VFS Development: If you are writing a custom VFS, implement xFileControl to handle opcodes your VFS supports and return SQLITE_NOTFOUND for others. Opcodes less than 100 are reserved by SQLite core—use opcodes above 100 for custom extensions.
Version Compatibility: New opcodes are sometimes added between SQLite versions. For example, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE was added in SQLite 3.11.0 (2016). If upgrading SQLite, rebuild your application to ensure you're using the correct header files and libraries.
ZipVFS and RBU: These are specialized VFS implementations. ZipVFS only implements SQLITE_FCNTL_ZIPVFS. RBU only implements SQLITE_FCNTL_RBU. Attempting to call other opcodes on these VFS implementations will return SQLITE_NOTFOUND. For the full set of opcodes, use the default VFS.
SQLITE_CORRUPT_VTAB: Content in virtual table is corrupt
Content in virtual table is corrupt
SQLITE_IOERR_WRITE: Disk I/O error during write
Disk I/O error during write operation
SQLITE_READONLY: Attempt to write a readonly database
How to fix "SQLITE_READONLY: Attempt to write a readonly database" in SQLite
SQLITE_CONSTRAINT_PRIMARYKEY: PRIMARY KEY constraint failed
How to fix "SQLITE_CONSTRAINT_PRIMARYKEY" in SQLite
SQLITE_READONLY_DBMOVED: Database file has been moved since opened
How to fix 'SQLITE_READONLY_DBMOVED: Database file has been moved since opened'