Devolutions.Ahtola.Data.Sqlite exposes a Microsoft.Data.Sqlite-shaped API
(Ahtola.Data.Sqlite.SqliteConnection, SqliteCommand, …) over the managed
engine, and emulates parts of System.Data.SQLite so applications that persisted
System.Data.SQLite connection strings and files keep working. This page lists what
behaves differently from each provider and from native SQLite, and the switches
that restore the behaviour you depend on.
| Area | Microsoft.Data.Sqlite | Ahtola facade | How to match |
|---|---|---|---|
Guid parameters |
Bound as uppercase TEXT |
Bound as a 16-byte BLOB (.NET byte layout) because BinaryGUID defaults to true, as in System.Data.SQLite |
BinaryGUID=False binds GUIDs as uppercase TEXT, exactly like Microsoft.Data.Sqlite, and reads 16-byte blobs back in RFC 4122 order |
DbType on parameters |
Ignored for binding; the value picks the storage class | Picks the storage class when the value can be stored that way. DbType.Guid accepts a Guid or a GUID string and follows BinaryGUID |
— |
GetValue on GUID/UNIQUEIDENTIFIER columns |
Returns the stored value | Returns a Guid when the value parses as one, and the stored value otherwise (legacy rows, text another client wrote). GetGuid still throws for non-GUID content |
— |
GetFieldType of BLOB columns |
byte[] |
object, so a DataAdapter/DataTable keeps text that SQLite's dynamic typing stored in a BLOB column |
Type Mapping=SystemDataSQLite reports byte[] |
*ID columns holding 16-byte blobs |
Returned as byte[] |
Returned as byte[] |
Guid Column Name Heuristic=True turns them into uppercase GUID strings when BinaryGUID is on (the behaviour of earlier Ahtola versions) |
Local Provider |
n/a | Managed (the only shipped engine); SqliteConnectionStringBuilder.LocalProvider reports it |
— |
| Wrong encryption key | n/a | SqliteException with SqliteErrorCode 26 (SQLITE_NOTADB); the message starts with file is encrypted or is not a database and the engine's diagnosis is the InnerException |
— |
| Lock contention | SqliteException 5 |
SqliteException 5 (SQLITE_BUSY), for engine and storage-level lock conflicts alike; the storage diagnosis is the InnerException |
— |
The builder accepts System.Data.SQLite's keywords and translates them:
| Keyword | Effect |
|---|---|
Journal Mode |
Applied as PRAGMA journal_mode on every open. A database the open creates is created directly in that mode, so Journal Mode=Delete never leaves -wal/-shm files behind |
Synchronous |
PRAGMA synchronous on open |
Read Only=True |
Mode=ReadOnly (when Mode is not set) |
FailIfMissing=True |
Mode=ReadWrite: opening a missing file fails with SQLITE_CANTOPEN (when Mode is not set) |
Page Size, Cache Size |
PRAGMA page_size / PRAGMA cache_size on open |
BusyTimeout (Busy Timeout) |
PRAGMA busy_timeout (milliseconds) on open |
Password |
Opens or creates the file with System.Data.SQLite's RC4 page format; existing wxSQLite3/sqlite3secure AES-128 files are detected and opened too (see Passwords) |
Legacy Format, Version |
Accepted and ignored |
Ignore Unknown Keywords=True |
Ignores keywords the provider does not know instead of throwing ArgumentException, wherever it appears in the string; the ignored pairs are listed in SqliteConnectionStringBuilder.IgnoredKeywords |
Auto Enlist Transaction=True |
Commands without a Transaction run inside the connection's pending transaction (System.Data.SQLite's behaviour) instead of failing with InvalidOperationException |
Type Mapping=SystemDataSQLite |
Declared types drive the CLR type, as in System.Data.SQLite (below) |
With Type Mapping=SystemDataSQLite, GetValue, GetFieldType,
GetSchemaTable().DataType (used by DataTable.Load and DbDataAdapter.Fill) and
ExecuteScalar follow System.Data.SQLite's declared-type table:
| Declared type (length suffix ignored) | CLR type |
|---|---|
INTEGER, BIGINT, INT64, LONG, COUNTER, IDENTITY |
long |
INT, INT32, MEDIUMINT |
int |
SMALLINT, INT16 |
short |
TINYINT |
byte |
BIT, BOOL, BOOLEAN, LOGICAL, YESNO |
bool |
DATE, DATETIME, DATETIME2, SMALLDATE, TIME, TIMESTAMP |
DateTime |
MONEY, CURRENCY, DECIMAL, NUMERIC, NUMBER |
decimal |
REAL, FLOAT, DOUBLE |
double; SINGLE is float |
GUID, UNIQUEIDENTIFIER |
Guid |
TEXT, VARCHAR, NVARCHAR, CHAR, CLOB, MEMO, … |
string |
BLOB, BINARY, VARBINARY, IMAGE, RAW, … |
byte[] |
A value stored with another storage class than its column declares (SQLite's dynamic typing) is returned as stored.
SQLiteConnection.CreateFile makes a zero-length file. Ahtola opens an existing
empty file as a new database, as native SQLite does, unless a rollback journal or
WAL sits next to it (an interrupted rewrite whose content must be recovered).
Ahtola.Data.Sqlite.Codecs ships IPageCodec implementations of the formats
native encryption extensions wrote, validated against files those engines produced:
| Codec | Format |
|---|---|
SystemDataSQLiteRc4PageCodec(password) |
System.Data.SQLite Password/ChangePassword (CryptoAPI RC4, key SHA1(UTF-8 password)[0..16], whole pages, header included) |
WxSQLite3Aes128PageCodec(password) |
wxSQLite3 4.x / sqlite3secure aes128cbc (MD5/RC4 key derivation, per-page AES-128-CBC; page 1 keeps bytes 16–23 readable; legacy whole-page mode is read too) |
Use them through the Password keyword, SqliteConnection.PageCodec, or
SqliteConnection.PageCodecCandidates (several codecs; the first whose
IPageCodec.MatchesHeader accepts the file is used, SelectedPageCodec reports
it). LegacyPageCodecs.Detect/DetectFile identify a file's format for a
password. These formats are unauthenticated and use RC4/MD5: keep them for opening
existing files, and prefer the built-in authenticated encryption
(Encryption Cipher/Encryption Key) for new data.
SqliteConnection.ChangePageCodec(newCodec) replaces System.Data.SQLite's
ChangePassword: it re-encodes every page with the new codec (null writes a plain
file), keeps the journal mode, and swaps the file atomically, so a crash leaves the
old or the new database intact. Every other connection to the file must be closed;
the method fails with SQLITE_BUSY otherwise.
Writing an IPageCodec:
CodecIdnames the format (algorithm, key derivation, layout), not the key. It is only checked to be non-zero and is never persisted; connections that set a codec are not pooled.- The connection does not own the codec: dispose disposable codecs after every connection that uses them is closed.
EncodePage/DecodePagemay run on different threads and concurrently when a codec instance is shared, so they must be thread-safe.- Throw
PageCodecKeyMismatchExceptionwhen the input proves the key is wrong; providers report it asSQLITE_NOTADB. ImplementMatchesHeaderso candidates can be told apart and a wrong key is reported before any page is read.
- Eager open.
Open()validates the file (header, encryption key or codec) and fails there; native SQLite and System.Data.SQLite open lazily and fail on the first statement. Code that probed a file by opening it with a key (for example aDatabaseExistshelper) must treat anOpen()failure as "exists but unreadable", not as "missing". - New databases default to WAL. Native SQLite, Microsoft.Data.Sqlite and
System.Data.SQLite create rollback-journal databases. Set
Journal Mode=Deleteto create them that way (recommended on network shares, where WAL is unsafe). - WAL sidecars. Closing the last connection to a file (including
ClearPool/ClearAllPoolsfor pooled connections) checkpoints the WAL and, when no other connection in any process has the database open, deletes the empty-waland the-shm, as native SQLite does. - Locking.
BEGIN IMMEDIATE(BeginTransaction()/BeginTransaction(deferred: false)) and a deferred transaction's first write take the write lock (RESERVED, or the WAL write lock) and hold it until the transaction ends, so other connections and processes cannot write meanwhile. Lock waits honourDefault TimeoutandPRAGMA busy_timeoutacross processes. ACOMMITthat fails withSQLITE_BUSYkeeps the transaction and its lock, and can be retried. In WAL mode, opening a database never waits for another process's writer. - Writes with an open reader.
INSERT,UPDATE,DELETEandINSERT … RETURNINGrun on a connection that still has a data reader open, as in SQLite. Schema changes,VACUUMandATTACH/DETACHstill wait for the connection's readers. locking_mode=EXCLUSIVEis taken lazily: the pragma succeeds and the next statement reportsSQLITE_BUSYwhile another connection holds the database.- Pragmas.
wal_autocheckpointdrives the checkpoint threshold (default 1000 frames;0disables the automatic checkpoint, closing still checkpoints).journal_size_limitis recorded and reported only: the managed engine always truncates a reset WAL to its header and deletes the rollback journal after each commit.PRAGMA auto_vacuum = FULL|INCREMENTALon a database that already has tables is accepted without effect (SQLite applies it at the nextVACUUM; Ahtola never enables auto-vacuum).cache_sizeis accepted and not reported. - Read-only WAL databases without
-shm(after a crash, or a copied WAL workspace) open read-only through a process-local WAL index instead of failing.