dbgate-sqlite-dumper 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +674 -0
- package/README.md +230 -0
- package/dist/better-sqlite3.cjs +178 -0
- package/dist/better-sqlite3.cjs.map +1 -0
- package/dist/better-sqlite3.d.cts +55 -0
- package/dist/better-sqlite3.d.ts +55 -0
- package/dist/better-sqlite3.js +140 -0
- package/dist/better-sqlite3.js.map +1 -0
- package/dist/index.cjs +3902 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1893 -0
- package/dist/index.d.ts +1893 -0
- package/dist/index.js +3775 -0
- package/dist/index.js.map +1 -0
- package/dist/types-CTyJTzB6.d.cts +151 -0
- package/dist/types-CTyJTzB6.d.ts +151 -0
- package/docs/architecture.md +157 -0
- package/docs/better-sqlite3-adapter.md +83 -0
- package/docs/dump-api.md +253 -0
- package/docs/known-limitations.md +88 -0
- package/docs/native-compatibility.md +124 -0
- package/docs/restore-api.md +234 -0
- package/docs/round-trip-testing.md +84 -0
- package/docs/supported-data-types.md +74 -0
- package/docs/supported-objects.md +85 -0
- package/package.json +89 -0
package/docs/dump-api.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Dump API
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
dumpSqlite(connection, options, output, onProgress?, signal?): Promise<DumpResult>
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Runs a complete dump — acquire a handle, open a read snapshot, introspect, plan, render,
|
|
8
|
+
stream rows — writing plain SQL in the native `.dump` layout to `output`.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createWriteStream } from 'node:fs';
|
|
12
|
+
import { dumpSqlite } from 'dbgate-sqlite-dumper';
|
|
13
|
+
import { connectBetterSqlite3 } from 'dbgate-sqlite-dumper/better-sqlite3';
|
|
14
|
+
|
|
15
|
+
const { connection, close } = await connectBetterSqlite3('shop.db', { readonly: true });
|
|
16
|
+
try {
|
|
17
|
+
const result = await dumpSqlite(connection, {}, createWriteStream('shop.sql'));
|
|
18
|
+
console.log(`${result.rowsExported} rows in ${result.statementsWritten} statements`);
|
|
19
|
+
} finally {
|
|
20
|
+
await close();
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## `output`
|
|
25
|
+
|
|
26
|
+
Any `Writable`. Never ended or closed by this package — the caller owns its lifecycle.
|
|
27
|
+
|
|
28
|
+
A dump is written incrementally, honouring backpressure, so a multi-gigabyte database
|
|
29
|
+
dumps in constant memory. `output` may receive `Buffer` chunks as well as text: a `TEXT`
|
|
30
|
+
value holding bytes that are not valid UTF-8 is written raw, exactly as the native `.dump`
|
|
31
|
+
writes it, and cannot be routed through a JavaScript string without corruption.
|
|
32
|
+
|
|
33
|
+
## `DumpSqliteOptions`
|
|
34
|
+
|
|
35
|
+
| Option | Default | Meaning |
|
|
36
|
+
| ------------- | ------------ | ----------------------------------------------- |
|
|
37
|
+
| `mode` | `'full'` | `'full'`, `'schema-only'`, `'data-only'`. |
|
|
38
|
+
| `schemaName` | `'main'` | `main`, `temp`, or an attached database's name. |
|
|
39
|
+
| `selection` | everything | Per-name object filters; see below. |
|
|
40
|
+
| `objectKinds` | all `true` | Which kinds participate at all. |
|
|
41
|
+
| `render` | see below | Output shape. |
|
|
42
|
+
| `dataExport` | see below | Row rendering and batching. |
|
|
43
|
+
| `consistency` | `'snapshot'` | How a consistent view is obtained. |
|
|
44
|
+
|
|
45
|
+
The dump itself never names the schema (`CREATE TABLE t`, not `CREATE TABLE aux.t`), so a
|
|
46
|
+
dump of an attached database restores into whatever database it is run against — like a
|
|
47
|
+
native `.dump`.
|
|
48
|
+
|
|
49
|
+
### `mode`
|
|
50
|
+
|
|
51
|
+
- **`'full'`** — schema and rows: the native `.dump`.
|
|
52
|
+
- **`'data-only'`** — `INSERT` statements only, no frame: the native `.dump --data-only`.
|
|
53
|
+
See [data-only dumps](#data-only-dumps).
|
|
54
|
+
- **`'schema-only'`** — every `CREATE`, no rows, no `AUTOINCREMENT` counters, no statistics.
|
|
55
|
+
Virtual tables are created with their own `CREATE VIRTUAL TABLE`, so their module sets up
|
|
56
|
+
its shadow tables, which are therefore left out.
|
|
57
|
+
|
|
58
|
+
### `selection`
|
|
59
|
+
|
|
60
|
+
Names are exact identifiers, never patterns, compared the way SQLite compares them (ASCII
|
|
61
|
+
letters case-insensitively, nothing else folded).
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
selection: {
|
|
65
|
+
tables: ['orders', 'order_lines'], // omit for all
|
|
66
|
+
excludeTables: ['orders_archive'], // applied after `tables`
|
|
67
|
+
views: [...], excludeViews: [...],
|
|
68
|
+
triggers: [...], excludeTriggers: [...],
|
|
69
|
+
excludeIndexes: [...], // indexes otherwise follow their table
|
|
70
|
+
|
|
71
|
+
// Structure dumped, rows skipped — for a large log or cache table you still
|
|
72
|
+
// want recreated.
|
|
73
|
+
dataExcludedTables: ['request_log'],
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Selecting a virtual table selects its shadow tables (`docs` brings `docs_data`,
|
|
78
|
+
`docs_idx`, ...). A trigger whose table is not selected is dropped rather than orphaned,
|
|
79
|
+
and reported as `trigger-table-not-selected`. In a partial dump, the `sqlite_sequence` and
|
|
80
|
+
statistics rows are restricted to the selected tables, and only their counters are reset.
|
|
81
|
+
|
|
82
|
+
### `objectKinds`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
objectKinds: {
|
|
86
|
+
includeTables: true,
|
|
87
|
+
includeViews: true,
|
|
88
|
+
includeIndexes: true,
|
|
89
|
+
includeTriggers: true,
|
|
90
|
+
includeVirtualTables: true, // and their shadow tables
|
|
91
|
+
includeSystemTables: true, // sqlite_sequence, sqlite_stat*; false = native --nosys
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `render` — output shape
|
|
96
|
+
|
|
97
|
+
| Option | Default | Meaning |
|
|
98
|
+
| -------------------------- | --------- | ------------------------------------------------------------------ |
|
|
99
|
+
| `includeSessionGuards` | `true` | `PRAGMA foreign_keys=OFF;` / `BEGIN TRANSACTION;` … `COMMIT;` |
|
|
100
|
+
| `includeHeaderComments` | `true` | The defensive-mode warning line, when there are virtual tables. |
|
|
101
|
+
| `addDropStatements` | `false` | `DROP … IF EXISTS` before each `CREATE`. |
|
|
102
|
+
| `includeDatabaseSettings` | `false` | `PRAGMA user_version` / `application_id` before `COMMIT`. |
|
|
103
|
+
| `legacySchemaTableName` | `false` | Write `sqlite_master` instead of `sqlite_schema` (targets < 3.33). |
|
|
104
|
+
| `unsupportedFeaturePolicy` | `'error'` | `'warn-omit'` skips an entry that cannot be rendered instead. |
|
|
105
|
+
|
|
106
|
+
Notes on the ones that carry weight:
|
|
107
|
+
|
|
108
|
+
- **`includeSessionGuards: false`** produces a `session-guards-disabled` warning. Without
|
|
109
|
+
`foreign_keys=OFF`, tables that reference each other may not restore in creation order;
|
|
110
|
+
without the transaction, every `INSERT` is its own transaction and its own disk sync.
|
|
111
|
+
- **`includeDatabaseSettings`** is off to match the native `.dump`, which does not carry
|
|
112
|
+
`user_version`. Applications often track their schema migrations there, so a non-zero
|
|
113
|
+
value that is not being dumped is reported as `user-version-not-dumped`.
|
|
114
|
+
|
|
115
|
+
### `dataExport` — row rendering
|
|
116
|
+
|
|
117
|
+
| Option | Default | Meaning |
|
|
118
|
+
| --------------------- | ------- | ----------------------------------------------------------------------- |
|
|
119
|
+
| `preserveRowids` | `false` | `INSERT INTO t(rowid,…)` for hidden rowids; native `--preserve-rowids`. |
|
|
120
|
+
| `rawNewlines` | `false` | Raw newlines in literals instead of `replace()`; native `--newlines`. |
|
|
121
|
+
| `extendedInsert` | `false` | Multi-row `INSERT … VALUES (…),(…)`. |
|
|
122
|
+
| `maxRowsPerStatement` | `500` | With `extendedInsert`. |
|
|
123
|
+
| `maxStatementBytes` | 1 MiB | With `extendedInsert`; a single larger row is still emitted alone. |
|
|
124
|
+
| `streamBatchSize` | — | Hint passed to `connection.stream()`. |
|
|
125
|
+
|
|
126
|
+
- **`preserveRowids`**: a table without an `INTEGER PRIMARY KEY` has a hidden rowid, and
|
|
127
|
+
without this option a restore renumbers its rows from 1. That matters when anything
|
|
128
|
+
refers to rowids — an external-content FTS index, an application storing them.
|
|
129
|
+
- **`extendedInsert`** makes the file smaller and the restore faster, at the cost of native
|
|
130
|
+
byte identity. One row per statement is not slow in SQLite — the whole restore is one
|
|
131
|
+
transaction — so the default follows the native layout.
|
|
132
|
+
|
|
133
|
+
Rows are read in the table's natural order, with no `ORDER BY`, exactly like the native
|
|
134
|
+
`.dump`: rowid order for an ordinary table, primary-key order for a `WITHOUT ROWID` one. Two
|
|
135
|
+
dumps of an unchanged database are identical.
|
|
136
|
+
|
|
137
|
+
### `consistency`
|
|
138
|
+
|
|
139
|
+
- **`'snapshot'`** (default) — the whole dump runs inside one read transaction (a
|
|
140
|
+
`SAVEPOINT`, so it nests inside a transaction the caller already has open, and reads
|
|
141
|
+
what that transaction sees). In WAL mode that is a true snapshot and writers carry on; in
|
|
142
|
+
rollback-journal mode it holds a `SHARED` lock that keeps writers out until the dump
|
|
143
|
+
ends. It is the same mechanism the native `.dump` uses.
|
|
144
|
+
- **`'none'`** — no transaction; only safe when nothing else writes to the database.
|
|
145
|
+
|
|
146
|
+
## Data-only dumps
|
|
147
|
+
|
|
148
|
+
`.dump --data-only` — and `mode: 'data-only'`, which reproduces it — has two quirks that
|
|
149
|
+
matter when loading the rows into an existing schema:
|
|
150
|
+
|
|
151
|
+
- it carries each virtual table's rows twice: through the table, and as its shadow tables'
|
|
152
|
+
rows, which collide with the target table's own shadow rows;
|
|
153
|
+
- it inserts into `sqlite_sequence` and `sqlite_stat*` without preparing them, leaving
|
|
154
|
+
duplicate counters, or failing where the target was never analyzed.
|
|
155
|
+
|
|
156
|
+
For loading rows into an existing schema, this option set avoids both:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
await dumpSqlite(
|
|
160
|
+
connection,
|
|
161
|
+
{
|
|
162
|
+
mode: 'data-only',
|
|
163
|
+
objectKinds: { includeSystemTables: false },
|
|
164
|
+
selection: {
|
|
165
|
+
dataExcludedTables: ['docs_data', 'docs_idx', 'docs_content', 'docs_docsize', 'docs_config'],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
output,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// …and restore with
|
|
172
|
+
await restoreSqlDump({
|
|
173
|
+
connection: target,
|
|
174
|
+
source,
|
|
175
|
+
options: { transaction: 'wrap', disableForeignKeys: true },
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Create triggers after the load, or they fire for every restored row.
|
|
180
|
+
|
|
181
|
+
## `DumpResult`
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
{
|
|
185
|
+
bytesWritten: number;
|
|
186
|
+
rowsExported: number;
|
|
187
|
+
statementsWritten: number;
|
|
188
|
+
renderedDumpIds: readonly string[];
|
|
189
|
+
skippedDumpIds: readonly string[];
|
|
190
|
+
warnings: readonly SqliteDiagnostic[];
|
|
191
|
+
cancelled: boolean;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`cancelled` is `true` when the `AbortSignal` fired; the dump is truncated but nothing
|
|
196
|
+
throws, and the read transaction is ended cleanly.
|
|
197
|
+
|
|
198
|
+
## Progress
|
|
199
|
+
|
|
200
|
+
Phases, in order: `connecting`, `starting-snapshot`, `introspecting`, `detecting-version`,
|
|
201
|
+
`planning-archive`, `rendering-schema`, `exporting-data`, `finalizing`. Rendering events
|
|
202
|
+
carry `section`, `objectsProcessed`/`objectsTotal` and `objectName`; data events carry
|
|
203
|
+
`exportState` (`started`/`progress`/`finished`/`failed`/`cancelled`), `tableName`,
|
|
204
|
+
`rowsExported` and `bytesWritten`, every 1000 rows.
|
|
205
|
+
|
|
206
|
+
## Cancellation
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
const controller = new AbortController();
|
|
210
|
+
const result = await dumpSqlite(connection, {}, output, undefined, controller.signal);
|
|
211
|
+
if (result.cancelled) console.warn('dump truncated');
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Cancellation takes effect between rows. Buffered output is deliberately **not** flushed: a
|
|
215
|
+
dump cut off at an arbitrary row should look truncated, not complete.
|
|
216
|
+
|
|
217
|
+
## Composing the stages
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import {
|
|
221
|
+
introspectSqlite,
|
|
222
|
+
inspectDumpArchive,
|
|
223
|
+
renderPlainSql,
|
|
224
|
+
BufferDumpWriter,
|
|
225
|
+
} from 'dbgate-sqlite-dumper';
|
|
226
|
+
|
|
227
|
+
const { database, version } = await introspectSqlite(connection);
|
|
228
|
+
|
|
229
|
+
// Inspect the plan without rendering anything.
|
|
230
|
+
const archive = inspectDumpArchive(database, { mode: 'schema-only' });
|
|
231
|
+
for (const entry of archive.entries) {
|
|
232
|
+
console.log(entry.sequenceNumber, entry.section, entry.objectType, entry.name);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Render schema only, into memory, with no database access.
|
|
236
|
+
const writer = new BufferDumpWriter();
|
|
237
|
+
await renderPlainSql({ database, archive, writer, mode: 'schema-only', sourceVersion: version });
|
|
238
|
+
console.log(writer.toString());
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`exportTableDataAsInserts` streams one table's rows on its own:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { exportTableDataAsInserts } from 'dbgate-sqlite-dumper';
|
|
245
|
+
|
|
246
|
+
await exportTableDataAsInserts({
|
|
247
|
+
connection,
|
|
248
|
+
schemaName: 'main',
|
|
249
|
+
table: database.tables.find(table => table.name === 'orders')!,
|
|
250
|
+
writer,
|
|
251
|
+
options: { extendedInsert: true },
|
|
252
|
+
});
|
|
253
|
+
```
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Known limitations
|
|
2
|
+
|
|
3
|
+
What this package does not do, and why. The items in the first section are also returned by
|
|
4
|
+
`unsupportedFeatureDiagnostics()`, so a UI can list them without hardcoding the text.
|
|
5
|
+
|
|
6
|
+
## Not dumped
|
|
7
|
+
|
|
8
|
+
### File-level settings
|
|
9
|
+
|
|
10
|
+
`page_size`, `auto_vacuum`, `journal_mode`, `encoding` and the like describe how the
|
|
11
|
+
database _file_ is laid out, not what it contains, and most can only be chosen before the
|
|
12
|
+
first table exists. The native `.dump` does not carry them either. Set them on the target
|
|
13
|
+
before restoring if they matter; the dump restores correctly under any of them.
|
|
14
|
+
|
|
15
|
+
`user_version` and `application_id` are the exception: they are carried with
|
|
16
|
+
`render.includeDatabaseSettings`, and reported as a warning when they are non-zero and not
|
|
17
|
+
carried.
|
|
18
|
+
|
|
19
|
+
### The temp schema and attached databases
|
|
20
|
+
|
|
21
|
+
A dump covers one schema — `main` by default. Temporary objects belong to one connection;
|
|
22
|
+
each attached database is a separate file. Dump an attached database on its own with
|
|
23
|
+
`schemaName`.
|
|
24
|
+
|
|
25
|
+
### Application-defined functions, collations and modules
|
|
26
|
+
|
|
27
|
+
User-defined SQL functions, collating sequences and virtual-table modules are registered by
|
|
28
|
+
the application (or loaded as extensions), not stored in the database. Schema that uses
|
|
29
|
+
them is dumped verbatim, and the restoring application must register them first.
|
|
30
|
+
`checkTargetCompatibility` reports custom collations used by indexes, and — when the
|
|
31
|
+
target's module list is known — missing virtual-table modules.
|
|
32
|
+
|
|
33
|
+
### Encryption
|
|
34
|
+
|
|
35
|
+
An encrypted database (SQLCipher, SEE) dumps as plain SQL once it is opened with its key.
|
|
36
|
+
The dump is not encrypted, and no key is ever written into it or into any diagnostic.
|
|
37
|
+
|
|
38
|
+
## Behaviours to know about
|
|
39
|
+
|
|
40
|
+
### `REAL` digits depend on the SQLite release
|
|
41
|
+
|
|
42
|
+
See [native-compatibility.md](native-compatibility.md#the-one-exception-real-digit-tails).
|
|
43
|
+
The value never changes; the spelling past the 17th digit can.
|
|
44
|
+
|
|
45
|
+
### `sqlite_stat4` across builds
|
|
46
|
+
|
|
47
|
+
`sqlite_stat4` exists only on SQLite built with `SQLITE_ENABLE_STAT4` (such as
|
|
48
|
+
`better-sqlite3`'s). A dump that carries its rows cannot be restored on a build without
|
|
49
|
+
it — by the native `.dump` either — because the `ANALYZE sqlite_schema;` that prepares the
|
|
50
|
+
table does not create it there. `preflightRestore` reports `statistics-table-unsupported`;
|
|
51
|
+
dump with `objectKinds.includeSystemTables: false`, or drop the table first.
|
|
52
|
+
|
|
53
|
+
Conversely, restoring through a STAT4 build creates an empty `sqlite_stat4` the source did
|
|
54
|
+
not have. It is harmless, and `ANALYZE` repopulates it.
|
|
55
|
+
|
|
56
|
+
### Data-only dumps
|
|
57
|
+
|
|
58
|
+
A data-only dump reproduces `.dump --data-only`, including two quirks that matter when
|
|
59
|
+
loading the rows into an existing schema (virtual-table rows carried twice; system tables
|
|
60
|
+
not prepared). [dump-api.md](dump-api.md#data-only-dumps) gives the option set that avoids
|
|
61
|
+
both.
|
|
62
|
+
|
|
63
|
+
### Hidden rowids are renumbered by default
|
|
64
|
+
|
|
65
|
+
As natively; see [supported-objects.md](supported-objects.md#hidden-rowids).
|
|
66
|
+
|
|
67
|
+
### Virtual-table dumps need defensive mode off to restore
|
|
68
|
+
|
|
69
|
+
Inherent to how the native `.dump` recreates virtual tables. The restore lifts
|
|
70
|
+
`SQLITE_DBCONFIG_DEFENSIVE` itself through adapters that can (the bundled one can); with an
|
|
71
|
+
adapter that cannot, the restore succeeds only on a handle not in defensive mode.
|
|
72
|
+
|
|
73
|
+
### Cancellation granularity
|
|
74
|
+
|
|
75
|
+
`better-sqlite3` runs each statement synchronously, so cancellation takes effect between
|
|
76
|
+
rows and between statements, never inside one.
|
|
77
|
+
|
|
78
|
+
### Memory
|
|
79
|
+
|
|
80
|
+
A dump holds one statement and a 64 KiB output buffer; a restore holds one statement's
|
|
81
|
+
text. A single enormous value — a 500 MB blob — is held whole, once, because one row cannot
|
|
82
|
+
be split.
|
|
83
|
+
|
|
84
|
+
### Scripts the parser refuses
|
|
85
|
+
|
|
86
|
+
Dot-commands that change the database (`.read`, `.import`, `.open`, …), identifiers
|
|
87
|
+
containing bytes that are not valid UTF-8, and input ending inside a string or quoted
|
|
88
|
+
identifier. See [restore-api.md](restore-api.md#errors).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Native compatibility
|
|
2
|
+
|
|
3
|
+
The promise, in both directions:
|
|
4
|
+
|
|
5
|
+
1. **A dump written by this package restores with the native shell**:
|
|
6
|
+
`sqlite3 target.db < dump.sql`.
|
|
7
|
+
2. **A dump written by the native `.dump` restores with this package**:
|
|
8
|
+
`restoreSqlDump({ connection, source })`.
|
|
9
|
+
|
|
10
|
+
Both are tested on every run against the real `sqlite3` shell (3.45 on Ubuntu 24.04 in CI),
|
|
11
|
+
ending with a deep comparison of the restored database's schema and every row against the
|
|
12
|
+
source. See [round-trip-testing.md](round-trip-testing.md).
|
|
13
|
+
|
|
14
|
+
## Byte identity
|
|
15
|
+
|
|
16
|
+
With default options, `dumpSqlite` writes the same bytes as `sqlite3 db .dump`, and each
|
|
17
|
+
native switch has an equivalent that is tested the same way:
|
|
18
|
+
|
|
19
|
+
| Native | This package |
|
|
20
|
+
| ------------------------- | ------------------------------------------------- |
|
|
21
|
+
| `.dump` | `{}` |
|
|
22
|
+
| `.dump --data-only` | `{ mode: 'data-only' }` |
|
|
23
|
+
| `.dump --preserve-rowids` | `{ dataExport: { preserveRowids: true } }` |
|
|
24
|
+
| `.dump --newlines` | `{ dataExport: { rawNewlines: true } }` |
|
|
25
|
+
| `.dump --nosys` | `{ objectKinds: { includeSystemTables: false } }` |
|
|
26
|
+
|
|
27
|
+
### The one exception: `REAL` digit tails
|
|
28
|
+
|
|
29
|
+
The shell writes a non-integral `REAL` with `sqlite3_snprintf("%!.20g")`, and this package
|
|
30
|
+
has SQLite evaluate `printf('%!.20g', value)` for it — the same formatter. But the digits
|
|
31
|
+
that formatter produces past the 17th significant digit changed between SQLite releases
|
|
32
|
+
(3.45 writes `0.100000000000000005`, 3.53 writes `0.1000000000000000056`). A driver that
|
|
33
|
+
bundles its own SQLite — as `better-sqlite3` does — therefore writes the digits of _its_
|
|
34
|
+
release.
|
|
35
|
+
|
|
36
|
+
Those digits carry no information: 17 significant digits already identify a double
|
|
37
|
+
exactly, and both spellings parse back to the identical value. The interop tests compare
|
|
38
|
+
every line byte for byte, and compare a differing line again with each `REAL` literal
|
|
39
|
+
parsed as a double; nothing else is allowed to differ. When the driver and the shell embed
|
|
40
|
+
the same SQLite release, the output is byte-identical without exception.
|
|
41
|
+
|
|
42
|
+
## What is reproduced, and why
|
|
43
|
+
|
|
44
|
+
Every rule below is ported from `shell.c`, because each one changes bytes in the output.
|
|
45
|
+
|
|
46
|
+
- **The frame.** `PRAGMA foreign_keys=OFF;` and `BEGIN TRANSACTION;` first, `COMMIT;`
|
|
47
|
+
last. A data-only dump has neither, as natively.
|
|
48
|
+
- **Creation order.** Tables in `sqlite_schema` rowid order (`sqlite_sequence` moved last),
|
|
49
|
+
each followed by its rows; then indexes, triggers and views in rowid order. Creation order
|
|
50
|
+
is a valid dependency order for everything SQLite checks at `CREATE` time, and
|
|
51
|
+
`foreign_keys=OFF` covers foreign keys, circular ones included.
|
|
52
|
+
- **Verbatim DDL.** SQLite stores every object's `CREATE` statement exactly as written; that
|
|
53
|
+
text is what is emitted, never a reconstruction.
|
|
54
|
+
- **`printSchemaLine()`.** A table whose name is quoted with `'` or `"` becomes
|
|
55
|
+
`CREATE TABLE IF NOT EXISTS`; DDL ending in a `--` comment or an unterminated `/*` gets a
|
|
56
|
+
newline or `*/` before its `;`, chosen with `sqlite3_complete()` exactly as the shell does.
|
|
57
|
+
- **Index, trigger and view terminators.** The statement's `;` goes on a line of its own if
|
|
58
|
+
its text contains `--` anywhere.
|
|
59
|
+
- **Identifier quoting.** Table and column names are quoted in `INSERT` only when not a
|
|
60
|
+
plain identifier or when a keyword (`quoteChar()`, with SQLite's 147 keywords).
|
|
61
|
+
- **Values.** Integers as decimal; `REAL` as `%lld.0` when integral and in range, else
|
|
62
|
+
`%!.20g`, with infinities as `9.0e+999`; blobs as lower-case `X'...'`; text quoted with
|
|
63
|
+
`'` doubled, and — by default — newlines and carriage returns rewritten as
|
|
64
|
+
`replace('...\n...','\n',char(10))`, with a placeholder guaranteed not to occur in the
|
|
65
|
+
text (`unused_string()`).
|
|
66
|
+
- **Virtual tables.** Written as `INSERT INTO sqlite_schema(...)` under
|
|
67
|
+
`PRAGMA writable_schema=ON`, not as `CREATE VIRTUAL TABLE`, so the module's constructor
|
|
68
|
+
does not run and the shadow tables — which the dump then fills with the source's exact
|
|
69
|
+
contents — are not rebuilt. The whole dump is preceded by
|
|
70
|
+
`/* WARNING: Script requires that SQLITE_DBCONFIG_DEFENSIVE be disabled */`.
|
|
71
|
+
- **System tables.** `DELETE FROM sqlite_sequence;` before the `AUTOINCREMENT` counters,
|
|
72
|
+
`ANALYZE sqlite_schema;` before each statistics table's rows.
|
|
73
|
+
|
|
74
|
+
## Native quirks reproduced deliberately
|
|
75
|
+
|
|
76
|
+
These are faithful to the shell rather than "fixed", because a dump that differs from the
|
|
77
|
+
native one for no stated reason is harder to trust than one with a documented quirk. Each is
|
|
78
|
+
reported as a warning when it applies.
|
|
79
|
+
|
|
80
|
+
- **`.dump --data-only` selects virtual tables' rows twice** — through the table itself,
|
|
81
|
+
and again as the shadow tables' rows — because the shell checks for data-only before it
|
|
82
|
+
checks for a virtual table. Restoring such a dump into a database where the virtual table
|
|
83
|
+
exists conflicts with its own shadow rows (`data-only-virtual-table`).
|
|
84
|
+
- **`.dump --data-only` inserts into system tables without preparing them**: the
|
|
85
|
+
`sqlite_sequence` rows are not preceded by a `DELETE`, and the statistics rows by no
|
|
86
|
+
`ANALYZE` (`data-only-system-table`).
|
|
87
|
+
- **`-0.0` is written as `0.0`**, because the shell's integral test is true for it.
|
|
88
|
+
|
|
89
|
+
[dump-api.md](dump-api.md#data-only-dumps) gives the option set that avoids the first two
|
|
90
|
+
when loading rows into an existing schema.
|
|
91
|
+
|
|
92
|
+
## Deliberate deviations
|
|
93
|
+
|
|
94
|
+
Only where the native behaviour loses data, and never for data the shell can represent:
|
|
95
|
+
|
|
96
|
+
- **`NUL` characters in text are kept.** The shell handles values as C strings and drops
|
|
97
|
+
everything after the first `NUL`; this package writes `'a'||char(0)||'b'`, which the
|
|
98
|
+
shell restores correctly. Text without `NUL` — all text in practice — is unaffected.
|
|
99
|
+
- **A schema-only dump** has no native `.dump` equivalent. It creates virtual tables with
|
|
100
|
+
their own `CREATE VIRTUAL TABLE` (as `.schema` shows them) and leaves their shadow tables
|
|
101
|
+
to the module, since no shadow rows follow.
|
|
102
|
+
- **Partial dumps** (a `selection`) keep only the selected tables' `sqlite_sequence` and
|
|
103
|
+
statistics rows, and reset only their counters
|
|
104
|
+
(`DELETE FROM sqlite_sequence WHERE lower(name) IN (...)`). The native `.dump PATTERN`
|
|
105
|
+
omits the counters of the selected tables entirely.
|
|
106
|
+
|
|
107
|
+
## Restoring native dumps
|
|
108
|
+
|
|
109
|
+
The restore side reproduces how the shell _reads_ a script, which is what makes any
|
|
110
|
+
`sqlite3` script restorable, not just `.dump` output:
|
|
111
|
+
|
|
112
|
+
- statement boundaries from `sqlite3_complete()`, so trigger bodies stay whole;
|
|
113
|
+
- `GO` and `/` lines as terminators, `#` lines at column 0 as comments, dot-commands at
|
|
114
|
+
column 0 as shell commands;
|
|
115
|
+
- a `\r` immediately before `\n` dropped everywhere, as the shell's line reader does;
|
|
116
|
+
- a leading UTF-8 byte-order mark ignored;
|
|
117
|
+
- a final statement without `;` still executed.
|
|
118
|
+
|
|
119
|
+
And it accounts for running inside an application instead of a process that exits:
|
|
120
|
+
|
|
121
|
+
- the defensive mode a virtual-table dump requires is lifted for the restore and put back;
|
|
122
|
+
- the schema is reloaded afterwards, so restored virtual tables work on the same handle;
|
|
123
|
+
- `foreign_keys` is put back to its previous value (a native dump never re-enables it);
|
|
124
|
+
- a transaction left open by a failing or truncated script is rolled back.
|