dbgate-sqlite-dumper 0.1.0 → 0.1.1
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 -674
- package/README.md +247 -230
- package/dist/better-sqlite3.cjs.map +1 -1
- package/dist/better-sqlite3.d.cts +1 -1
- package/dist/better-sqlite3.d.ts +1 -1
- package/dist/better-sqlite3.js.map +1 -1
- package/dist/d1.cjs +260 -0
- package/dist/d1.cjs.map +1 -0
- package/dist/d1.d.cts +101 -0
- package/dist/d1.d.ts +101 -0
- package/dist/d1.js +229 -0
- package/dist/d1.js.map +1 -0
- package/dist/errors-CplNzuYs.d.cts +20 -0
- package/dist/errors-CplNzuYs.d.ts +20 -0
- package/dist/index.cjs +405 -90
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -24
- package/dist/index.d.ts +58 -24
- package/dist/index.js +400 -89
- package/dist/index.js.map +1 -1
- package/dist/{types-CTyJTzB6.d.cts → types-DziV5ysl.d.cts} +67 -2
- package/dist/{types-CTyJTzB6.d.ts → types-DziV5ysl.d.ts} +67 -2
- package/docs/architecture.md +180 -157
- package/docs/better-sqlite3-adapter.md +83 -83
- package/docs/d1-adapter.md +98 -0
- package/docs/dump-api.md +253 -253
- package/docs/known-limitations.md +100 -88
- package/docs/native-compatibility.md +124 -124
- package/docs/restore-api.md +234 -234
- package/docs/round-trip-testing.md +84 -84
- package/docs/supported-data-types.md +74 -74
- package/docs/supported-objects.md +85 -85
- package/package.json +96 -89
package/docs/restore-api.md
CHANGED
|
@@ -1,234 +1,234 @@
|
|
|
1
|
-
# Restore API
|
|
2
|
-
|
|
3
|
-
```ts
|
|
4
|
-
restoreSqlDump({ connection, source, options?, progress?, signal? }): Promise<SqlDumpRestoreResult>
|
|
5
|
-
```
|
|
6
|
-
|
|
7
|
-
Restores a plain-SQL SQLite dump using only the `SqliteConnection` abstraction — no
|
|
8
|
-
`sqlite3` shell, no external process. Works on dumps produced by this package **and** on
|
|
9
|
-
dumps produced by the native `.dump`, and on any other script the shell would accept.
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import { createReadStream } from 'node:fs';
|
|
13
|
-
import { restoreSqlDump } from 'dbgate-sqlite-dumper';
|
|
14
|
-
|
|
15
|
-
const result = await restoreSqlDump({
|
|
16
|
-
connection,
|
|
17
|
-
source: createReadStream('shop.sql'),
|
|
18
|
-
progress: event => {
|
|
19
|
-
if (event.phase === 'executing' && event.executionState === 'finished') {
|
|
20
|
-
console.log(`${event.statementsProcessed} statements, ${event.rowsRestored} rows`);
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## `source`
|
|
27
|
-
|
|
28
|
-
```ts
|
|
29
|
-
type SqlDumpSource =
|
|
30
|
-
string | Buffer | Uint8Array | Readable | AsyncIterable<string | Buffer | Uint8Array>;
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Parsed incrementally: only the current statement's text plus a few carried bytes are held
|
|
34
|
-
at a time, so a multi-gigabyte dump never lands in memory.
|
|
35
|
-
|
|
36
|
-
`Buffer` is accepted alongside `string` for a reason that matters: the native `.dump`
|
|
37
|
-
writes a `TEXT` value's stored bytes verbatim, so a database holding text that is not valid
|
|
38
|
-
UTF-8 produces a dump that is not valid UTF-8 either. Pass the bytes; forcing them through
|
|
39
|
-
`.toString()` would replace every invalid sequence with U+FFFD.
|
|
40
|
-
|
|
41
|
-
## How a script is split
|
|
42
|
-
|
|
43
|
-
Exactly where the `sqlite3` shell splits it. Two layers, both ported from SQLite's sources:
|
|
44
|
-
|
|
45
|
-
| Construct | Behaviour |
|
|
46
|
-
| ------------------------------- | --------------------------------------------------------------------------------- |
|
|
47
|
-
| `'…'` | String; `''` is a quote. **No** backslash escapes — SQLite has none. |
|
|
48
|
-
| `"…"`, `` `…` ``, `[…]` | Quoted identifiers; a `;` inside never splits. |
|
|
49
|
-
| `-- …`, `/* … */` | Comments (block comments do not nest). A block comment may run to end of input. |
|
|
50
|
-
| `CREATE TRIGGER … BEGIN … END;` | One statement, inner `;`s included — the `sqlite3_complete()` state machine. |
|
|
51
|
-
| `GO` / `/` alone on a line | Ends the pending statement, as in the shell. |
|
|
52
|
-
| `.command` at column 0 | A shell dot-command; see below. |
|
|
53
|
-
| `# …` at column 0 | A comment line, as in the shell. |
|
|
54
|
-
| `\r\n` | The `\r` is dropped — everywhere, literals included — as the shell's reader does. |
|
|
55
|
-
| UTF-8 byte-order mark | Whitespace. |
|
|
56
|
-
| missing final `;` | The last statement still runs, as in the shell. |
|
|
57
|
-
|
|
58
|
-
The dot-command and `#` rules apply only when no SQL is pending — `SELECT 1\n.5;` is one
|
|
59
|
-
statement — exactly as the shell decides it.
|
|
60
|
-
|
|
61
|
-
`tests/statementParser.test.ts` asserts identical output at **every** chunk size and
|
|
62
|
-
**every** single split point; `integration/scripts.integration.test.ts` runs a set of
|
|
63
|
-
hand-written scripts through both the shell and this package and requires identical
|
|
64
|
-
databases afterwards.
|
|
65
|
-
|
|
66
|
-
### Dot-commands
|
|
67
|
-
|
|
68
|
-
The shell runs lines beginning with `.` itself. Under the default
|
|
69
|
-
`dotCommands: 'skip-presentational'`:
|
|
70
|
-
|
|
71
|
-
- commands that only affect the shell's own output (`.mode`, `.headers`, `.print`, `.echo`,
|
|
72
|
-
`.timer`, `.output`, …) are skipped and reported as `dot-command-skipped`;
|
|
73
|
-
- `.quit` and `.exit` end the input, as in the shell;
|
|
74
|
-
- every other command (`.read`, `.import`, `.open`, `.load`, `.shell`, `.parameter`, …) is
|
|
75
|
-
refused with `UnsupportedClientCommandError`. Silently skipping a `.read` would leave the
|
|
76
|
-
referenced file's objects missing — a corrupted restore that looks like a successful one.
|
|
77
|
-
|
|
78
|
-
`dotCommands: 'error'` refuses every dot-command.
|
|
79
|
-
|
|
80
|
-
### Text that is not valid UTF-8
|
|
81
|
-
|
|
82
|
-
A statement whose string literal holds such bytes is executed with that literal rewritten
|
|
83
|
-
to `(CAST(X'…' AS TEXT))`, which stores the identical bytes, and a `text-literal-rewritten`
|
|
84
|
-
warning says how many. Such bytes anywhere else — in an identifier — cannot be represented
|
|
85
|
-
and raise `InvalidTextEncodingError`.
|
|
86
|
-
|
|
87
|
-
## `RestoreOptions`
|
|
88
|
-
|
|
89
|
-
| Option | Default | Meaning |
|
|
90
|
-
| --------------------- | ----------------------- | ---------------------------------------------------------------------------- |
|
|
91
|
-
| `stopOnError` | `true` | Stop at the first failing statement. |
|
|
92
|
-
| `restoreSessionState` | `true` | Put the connection back as it was; see below. |
|
|
93
|
-
| `schemaWrites` | `'allow'` | Virtual-table dumps write `sqlite_schema`; `'refuse'` fails such statements. |
|
|
94
|
-
| `transaction` | `'script'` | `'wrap'`: run everything in one transaction of the restore's own. |
|
|
95
|
-
| `disableForeignKeys` | `false` | `PRAGMA foreign_keys=OFF` for the restore (data-only dumps). |
|
|
96
|
-
| `verifyForeignKeys` | `false` | Run `PRAGMA foreign_key_check` afterwards and report violations. |
|
|
97
|
-
| `maxStatementBytes` | 256 MiB | Bound on one statement's buffered text. |
|
|
98
|
-
| `dotCommands` | `'skip-presentational'` | See above. |
|
|
99
|
-
|
|
100
|
-
### `restoreSessionState`
|
|
101
|
-
|
|
102
|
-
A native dump is written for a shell that exits when it is done. Run through a long-lived
|
|
103
|
-
handle, three things would otherwise leak into the caller's session:
|
|
104
|
-
|
|
105
|
-
- **`foreign_keys`.** A dump's first line turns enforcement off, and nothing turns it back
|
|
106
|
-
on. It is returned to its value before the restore.
|
|
107
|
-
- **An open transaction.** A restore that stops at a failing statement, is cancelled, or
|
|
108
|
-
reads a dump truncated before its `COMMIT` would hand the handle back mid-transaction,
|
|
109
|
-
holding a write lock on the file. The transaction the _script_ opened is rolled back —
|
|
110
|
-
as the shell's exit would roll it back — and reported as `transaction-rolled-back`. A
|
|
111
|
-
transaction the caller opened before the restore is never touched.
|
|
112
|
-
- **`writable_schema`.** Turned back off if the script left it on.
|
|
113
|
-
|
|
114
|
-
### `schemaWrites`
|
|
115
|
-
|
|
116
|
-
A dump of a database with virtual tables recreates them by inserting into `sqlite_schema`
|
|
117
|
-
under `PRAGMA writable_schema=ON` — the native `.dump` does exactly this, and opens with a
|
|
118
|
-
comment saying it requires `SQLITE_DBCONFIG_DEFENSIVE` to be off. Under `'allow'`, when the
|
|
119
|
-
adapter can (`setDefensive`), defensive mode is lifted at the `writable_schema=ON`
|
|
120
|
-
statement, restored afterwards, and reported as `defensive-mode-disabled`. The schema is
|
|
121
|
-
then reloaded (`PRAGMA writable_schema=RESET`), so the restored virtual tables work on the
|
|
122
|
-
same handle — without it they would only appear on the next connection.
|
|
123
|
-
|
|
124
|
-
### `transaction: 'wrap'`
|
|
125
|
-
|
|
126
|
-
The restore opens its own transaction before the first statement that is not a `PRAGMA` —
|
|
127
|
-
so the dump's leading `PRAGMA foreign_keys=OFF` still takes effect — skips the script's own
|
|
128
|
-
`BEGIN`/`COMMIT`/`ROLLBACK`, and commits at the end, or rolls back everything on failure.
|
|
129
|
-
Meant for data-only dumps, which have no transaction of their own and would otherwise sync
|
|
130
|
-
to disk once per row.
|
|
131
|
-
|
|
132
|
-
## `SqlDumpRestoreResult`
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
{
|
|
136
|
-
statementsExecuted: number;
|
|
137
|
-
statementsFailed: number;
|
|
138
|
-
rowsRestored: number; // sum of rows changed
|
|
139
|
-
bytesConsumed: number;
|
|
140
|
-
errors: readonly RestoreStatementError[];
|
|
141
|
-
warnings: readonly RestoreWarning[];
|
|
142
|
-
cancelled: boolean;
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Errors
|
|
147
|
-
|
|
148
|
-
### Parse errors — always fatal, thrown
|
|
149
|
-
|
|
150
|
-
The statement boundaries themselves cannot be trusted past the failure point.
|
|
151
|
-
|
|
152
|
-
| Error | Cause |
|
|
153
|
-
| ------------------------------- | --------------------------------------------------------------------- |
|
|
154
|
-
| `MalformedSqlDumpError` | Input ends inside a string or quoted identifier — usually truncation. |
|
|
155
|
-
| `StatementTooLargeError` | One statement exceeded `maxStatementBytes`. |
|
|
156
|
-
| `UnsupportedClientCommandError` | A dot-command that would change the database. |
|
|
157
|
-
| `InvalidTextEncodingError` | Bytes that are not valid UTF-8 outside any string literal. |
|
|
158
|
-
|
|
159
|
-
All carry `line`, and all extend `SqlParseError` → `RestoreError` → `SqliteDumperError`
|
|
160
|
-
(which has a stable `code`).
|
|
161
|
-
|
|
162
|
-
### Execution errors — scoped to one statement
|
|
163
|
-
|
|
164
|
-
Recorded in `result.errors`; with `stopOnError: false` the restore continues.
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
{
|
|
168
|
-
statementIndex: number;
|
|
169
|
-
location: { startLine: number; endLine: number };
|
|
170
|
-
sqlPreview: string; // ≤200 chars, whitespace-collapsed, keys redacted
|
|
171
|
-
message: string;
|
|
172
|
-
sqliteError?: { code?: string; errno?: number; message: string };
|
|
173
|
-
}
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
`sqliteError.code` is SQLite's extended result code (`SQLITE_CONSTRAINT_UNIQUE`), so a
|
|
177
|
-
caller can branch on it instead of matching message text. `location.startLine` points at
|
|
178
|
-
the first real SQL character, not at the comments before it.
|
|
179
|
-
|
|
180
|
-
**No error or preview ever contains an encryption key.** `redactSecrets` covers the syntax
|
|
181
|
-
SQLCipher and the SQLite Encryption Extension use — `PRAGMA key`/`rekey`/`hexkey`/`textkey`
|
|
182
|
-
and `ATTACH … KEY` — and is applied to driver messages too.
|
|
183
|
-
|
|
184
|
-
## Progress
|
|
185
|
-
|
|
186
|
-
```ts
|
|
187
|
-
progress: event => {
|
|
188
|
-
// 'connecting' | 'parsing' | 'executing' | 'finalizing'
|
|
189
|
-
console.log(event.phase, event.statementIndex, event.currentObject, event.bytesConsumed);
|
|
190
|
-
};
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
`currentObject` is read from the statements themselves (`CREATE TABLE t`, `INSERT INTO t`),
|
|
194
|
-
since a native dump carries no section banners.
|
|
195
|
-
|
|
196
|
-
## Preflight
|
|
197
|
-
|
|
198
|
-
```ts
|
|
199
|
-
import { introspectSqlite, preflightRestore } from 'dbgate-sqlite-dumper';
|
|
200
|
-
|
|
201
|
-
const { database } = await introspectSqlite(source);
|
|
202
|
-
const report = await preflightRestore({ connection: target, database });
|
|
203
|
-
if (report.diagnostics.some(diagnostic => diagnostic.severity === 'error')) {
|
|
204
|
-
// e.g. object-already-exists, unsupported-target-feature (STRICT tables on 3.31),
|
|
205
|
-
// virtual-table-module-unavailable, statistics-table-unsupported
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
Turns a failure part-way through a restore into an up-front report. Also reports the
|
|
210
|
-
target's version, encoding, `foreign_keys`, open transaction, available modules and compile
|
|
211
|
-
options, and whether defensive mode can be lifted. Never writes to the target.
|
|
212
|
-
|
|
213
|
-
## Using the parser on its own
|
|
214
|
-
|
|
215
|
-
```ts
|
|
216
|
-
import {
|
|
217
|
-
parseSqlStatements,
|
|
218
|
-
streamSqlStatements,
|
|
219
|
-
isSqliteDump,
|
|
220
|
-
isCompleteStatement,
|
|
221
|
-
} from 'dbgate-sqlite-dumper';
|
|
222
|
-
|
|
223
|
-
if (!isSqliteDump(head)) throw new Error('not a SQLite dump');
|
|
224
|
-
|
|
225
|
-
for (const statement of parseSqlStatements(sql)) {
|
|
226
|
-
console.log(statement.statementIndex, statement.info.verb, statement.location.startLine);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
for await (const statement of streamSqlStatements(createReadStream('big.sql'))) {
|
|
230
|
-
// constant memory
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
isCompleteStatement('CREATE TRIGGER t AFTER INSERT ON x BEGIN SELECT 1;'); // false
|
|
234
|
-
```
|
|
1
|
+
# Restore API
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
restoreSqlDump({ connection, source, options?, progress?, signal? }): Promise<SqlDumpRestoreResult>
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Restores a plain-SQL SQLite dump using only the `SqliteConnection` abstraction — no
|
|
8
|
+
`sqlite3` shell, no external process. Works on dumps produced by this package **and** on
|
|
9
|
+
dumps produced by the native `.dump`, and on any other script the shell would accept.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { createReadStream } from 'node:fs';
|
|
13
|
+
import { restoreSqlDump } from 'dbgate-sqlite-dumper';
|
|
14
|
+
|
|
15
|
+
const result = await restoreSqlDump({
|
|
16
|
+
connection,
|
|
17
|
+
source: createReadStream('shop.sql'),
|
|
18
|
+
progress: event => {
|
|
19
|
+
if (event.phase === 'executing' && event.executionState === 'finished') {
|
|
20
|
+
console.log(`${event.statementsProcessed} statements, ${event.rowsRestored} rows`);
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## `source`
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
type SqlDumpSource =
|
|
30
|
+
string | Buffer | Uint8Array | Readable | AsyncIterable<string | Buffer | Uint8Array>;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Parsed incrementally: only the current statement's text plus a few carried bytes are held
|
|
34
|
+
at a time, so a multi-gigabyte dump never lands in memory.
|
|
35
|
+
|
|
36
|
+
`Buffer` is accepted alongside `string` for a reason that matters: the native `.dump`
|
|
37
|
+
writes a `TEXT` value's stored bytes verbatim, so a database holding text that is not valid
|
|
38
|
+
UTF-8 produces a dump that is not valid UTF-8 either. Pass the bytes; forcing them through
|
|
39
|
+
`.toString()` would replace every invalid sequence with U+FFFD.
|
|
40
|
+
|
|
41
|
+
## How a script is split
|
|
42
|
+
|
|
43
|
+
Exactly where the `sqlite3` shell splits it. Two layers, both ported from SQLite's sources:
|
|
44
|
+
|
|
45
|
+
| Construct | Behaviour |
|
|
46
|
+
| ------------------------------- | --------------------------------------------------------------------------------- |
|
|
47
|
+
| `'…'` | String; `''` is a quote. **No** backslash escapes — SQLite has none. |
|
|
48
|
+
| `"…"`, `` `…` ``, `[…]` | Quoted identifiers; a `;` inside never splits. |
|
|
49
|
+
| `-- …`, `/* … */` | Comments (block comments do not nest). A block comment may run to end of input. |
|
|
50
|
+
| `CREATE TRIGGER … BEGIN … END;` | One statement, inner `;`s included — the `sqlite3_complete()` state machine. |
|
|
51
|
+
| `GO` / `/` alone on a line | Ends the pending statement, as in the shell. |
|
|
52
|
+
| `.command` at column 0 | A shell dot-command; see below. |
|
|
53
|
+
| `# …` at column 0 | A comment line, as in the shell. |
|
|
54
|
+
| `\r\n` | The `\r` is dropped — everywhere, literals included — as the shell's reader does. |
|
|
55
|
+
| UTF-8 byte-order mark | Whitespace. |
|
|
56
|
+
| missing final `;` | The last statement still runs, as in the shell. |
|
|
57
|
+
|
|
58
|
+
The dot-command and `#` rules apply only when no SQL is pending — `SELECT 1\n.5;` is one
|
|
59
|
+
statement — exactly as the shell decides it.
|
|
60
|
+
|
|
61
|
+
`tests/statementParser.test.ts` asserts identical output at **every** chunk size and
|
|
62
|
+
**every** single split point; `integration/scripts.integration.test.ts` runs a set of
|
|
63
|
+
hand-written scripts through both the shell and this package and requires identical
|
|
64
|
+
databases afterwards.
|
|
65
|
+
|
|
66
|
+
### Dot-commands
|
|
67
|
+
|
|
68
|
+
The shell runs lines beginning with `.` itself. Under the default
|
|
69
|
+
`dotCommands: 'skip-presentational'`:
|
|
70
|
+
|
|
71
|
+
- commands that only affect the shell's own output (`.mode`, `.headers`, `.print`, `.echo`,
|
|
72
|
+
`.timer`, `.output`, …) are skipped and reported as `dot-command-skipped`;
|
|
73
|
+
- `.quit` and `.exit` end the input, as in the shell;
|
|
74
|
+
- every other command (`.read`, `.import`, `.open`, `.load`, `.shell`, `.parameter`, …) is
|
|
75
|
+
refused with `UnsupportedClientCommandError`. Silently skipping a `.read` would leave the
|
|
76
|
+
referenced file's objects missing — a corrupted restore that looks like a successful one.
|
|
77
|
+
|
|
78
|
+
`dotCommands: 'error'` refuses every dot-command.
|
|
79
|
+
|
|
80
|
+
### Text that is not valid UTF-8
|
|
81
|
+
|
|
82
|
+
A statement whose string literal holds such bytes is executed with that literal rewritten
|
|
83
|
+
to `(CAST(X'…' AS TEXT))`, which stores the identical bytes, and a `text-literal-rewritten`
|
|
84
|
+
warning says how many. Such bytes anywhere else — in an identifier — cannot be represented
|
|
85
|
+
and raise `InvalidTextEncodingError`.
|
|
86
|
+
|
|
87
|
+
## `RestoreOptions`
|
|
88
|
+
|
|
89
|
+
| Option | Default | Meaning |
|
|
90
|
+
| --------------------- | ----------------------- | ---------------------------------------------------------------------------- |
|
|
91
|
+
| `stopOnError` | `true` | Stop at the first failing statement. |
|
|
92
|
+
| `restoreSessionState` | `true` | Put the connection back as it was; see below. |
|
|
93
|
+
| `schemaWrites` | `'allow'` | Virtual-table dumps write `sqlite_schema`; `'refuse'` fails such statements. |
|
|
94
|
+
| `transaction` | `'script'` | `'wrap'`: run everything in one transaction of the restore's own. |
|
|
95
|
+
| `disableForeignKeys` | `false` | `PRAGMA foreign_keys=OFF` for the restore (data-only dumps). |
|
|
96
|
+
| `verifyForeignKeys` | `false` | Run `PRAGMA foreign_key_check` afterwards and report violations. |
|
|
97
|
+
| `maxStatementBytes` | 256 MiB | Bound on one statement's buffered text. |
|
|
98
|
+
| `dotCommands` | `'skip-presentational'` | See above. |
|
|
99
|
+
|
|
100
|
+
### `restoreSessionState`
|
|
101
|
+
|
|
102
|
+
A native dump is written for a shell that exits when it is done. Run through a long-lived
|
|
103
|
+
handle, three things would otherwise leak into the caller's session:
|
|
104
|
+
|
|
105
|
+
- **`foreign_keys`.** A dump's first line turns enforcement off, and nothing turns it back
|
|
106
|
+
on. It is returned to its value before the restore.
|
|
107
|
+
- **An open transaction.** A restore that stops at a failing statement, is cancelled, or
|
|
108
|
+
reads a dump truncated before its `COMMIT` would hand the handle back mid-transaction,
|
|
109
|
+
holding a write lock on the file. The transaction the _script_ opened is rolled back —
|
|
110
|
+
as the shell's exit would roll it back — and reported as `transaction-rolled-back`. A
|
|
111
|
+
transaction the caller opened before the restore is never touched.
|
|
112
|
+
- **`writable_schema`.** Turned back off if the script left it on.
|
|
113
|
+
|
|
114
|
+
### `schemaWrites`
|
|
115
|
+
|
|
116
|
+
A dump of a database with virtual tables recreates them by inserting into `sqlite_schema`
|
|
117
|
+
under `PRAGMA writable_schema=ON` — the native `.dump` does exactly this, and opens with a
|
|
118
|
+
comment saying it requires `SQLITE_DBCONFIG_DEFENSIVE` to be off. Under `'allow'`, when the
|
|
119
|
+
adapter can (`setDefensive`), defensive mode is lifted at the `writable_schema=ON`
|
|
120
|
+
statement, restored afterwards, and reported as `defensive-mode-disabled`. The schema is
|
|
121
|
+
then reloaded (`PRAGMA writable_schema=RESET`), so the restored virtual tables work on the
|
|
122
|
+
same handle — without it they would only appear on the next connection.
|
|
123
|
+
|
|
124
|
+
### `transaction: 'wrap'`
|
|
125
|
+
|
|
126
|
+
The restore opens its own transaction before the first statement that is not a `PRAGMA` —
|
|
127
|
+
so the dump's leading `PRAGMA foreign_keys=OFF` still takes effect — skips the script's own
|
|
128
|
+
`BEGIN`/`COMMIT`/`ROLLBACK`, and commits at the end, or rolls back everything on failure.
|
|
129
|
+
Meant for data-only dumps, which have no transaction of their own and would otherwise sync
|
|
130
|
+
to disk once per row.
|
|
131
|
+
|
|
132
|
+
## `SqlDumpRestoreResult`
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
{
|
|
136
|
+
statementsExecuted: number;
|
|
137
|
+
statementsFailed: number;
|
|
138
|
+
rowsRestored: number; // sum of rows changed
|
|
139
|
+
bytesConsumed: number;
|
|
140
|
+
errors: readonly RestoreStatementError[];
|
|
141
|
+
warnings: readonly RestoreWarning[];
|
|
142
|
+
cancelled: boolean;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Errors
|
|
147
|
+
|
|
148
|
+
### Parse errors — always fatal, thrown
|
|
149
|
+
|
|
150
|
+
The statement boundaries themselves cannot be trusted past the failure point.
|
|
151
|
+
|
|
152
|
+
| Error | Cause |
|
|
153
|
+
| ------------------------------- | --------------------------------------------------------------------- |
|
|
154
|
+
| `MalformedSqlDumpError` | Input ends inside a string or quoted identifier — usually truncation. |
|
|
155
|
+
| `StatementTooLargeError` | One statement exceeded `maxStatementBytes`. |
|
|
156
|
+
| `UnsupportedClientCommandError` | A dot-command that would change the database. |
|
|
157
|
+
| `InvalidTextEncodingError` | Bytes that are not valid UTF-8 outside any string literal. |
|
|
158
|
+
|
|
159
|
+
All carry `line`, and all extend `SqlParseError` → `RestoreError` → `SqliteDumperError`
|
|
160
|
+
(which has a stable `code`).
|
|
161
|
+
|
|
162
|
+
### Execution errors — scoped to one statement
|
|
163
|
+
|
|
164
|
+
Recorded in `result.errors`; with `stopOnError: false` the restore continues.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
{
|
|
168
|
+
statementIndex: number;
|
|
169
|
+
location: { startLine: number; endLine: number };
|
|
170
|
+
sqlPreview: string; // ≤200 chars, whitespace-collapsed, keys redacted
|
|
171
|
+
message: string;
|
|
172
|
+
sqliteError?: { code?: string; errno?: number; message: string };
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`sqliteError.code` is SQLite's extended result code (`SQLITE_CONSTRAINT_UNIQUE`), so a
|
|
177
|
+
caller can branch on it instead of matching message text. `location.startLine` points at
|
|
178
|
+
the first real SQL character, not at the comments before it.
|
|
179
|
+
|
|
180
|
+
**No error or preview ever contains an encryption key.** `redactSecrets` covers the syntax
|
|
181
|
+
SQLCipher and the SQLite Encryption Extension use — `PRAGMA key`/`rekey`/`hexkey`/`textkey`
|
|
182
|
+
and `ATTACH … KEY` — and is applied to driver messages too.
|
|
183
|
+
|
|
184
|
+
## Progress
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
progress: event => {
|
|
188
|
+
// 'connecting' | 'parsing' | 'executing' | 'finalizing'
|
|
189
|
+
console.log(event.phase, event.statementIndex, event.currentObject, event.bytesConsumed);
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`currentObject` is read from the statements themselves (`CREATE TABLE t`, `INSERT INTO t`),
|
|
194
|
+
since a native dump carries no section banners.
|
|
195
|
+
|
|
196
|
+
## Preflight
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { introspectSqlite, preflightRestore } from 'dbgate-sqlite-dumper';
|
|
200
|
+
|
|
201
|
+
const { database } = await introspectSqlite(source);
|
|
202
|
+
const report = await preflightRestore({ connection: target, database });
|
|
203
|
+
if (report.diagnostics.some(diagnostic => diagnostic.severity === 'error')) {
|
|
204
|
+
// e.g. object-already-exists, unsupported-target-feature (STRICT tables on 3.31),
|
|
205
|
+
// virtual-table-module-unavailable, statistics-table-unsupported
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Turns a failure part-way through a restore into an up-front report. Also reports the
|
|
210
|
+
target's version, encoding, `foreign_keys`, open transaction, available modules and compile
|
|
211
|
+
options, and whether defensive mode can be lifted. Never writes to the target.
|
|
212
|
+
|
|
213
|
+
## Using the parser on its own
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import {
|
|
217
|
+
parseSqlStatements,
|
|
218
|
+
streamSqlStatements,
|
|
219
|
+
isSqliteDump,
|
|
220
|
+
isCompleteStatement,
|
|
221
|
+
} from 'dbgate-sqlite-dumper';
|
|
222
|
+
|
|
223
|
+
if (!isSqliteDump(head)) throw new Error('not a SQLite dump');
|
|
224
|
+
|
|
225
|
+
for (const statement of parseSqlStatements(sql)) {
|
|
226
|
+
console.log(statement.statementIndex, statement.info.verb, statement.location.startLine);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for await (const statement of streamSqlStatements(createReadStream('big.sql'))) {
|
|
230
|
+
// constant memory
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
isCompleteStatement('CREATE TRIGGER t AFTER INSERT ON x BEGIN SELECT 1;'); // false
|
|
234
|
+
```
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
# Round-trip testing
|
|
2
|
-
|
|
3
|
-
Two suites, kept apart on purpose.
|
|
4
|
-
|
|
5
|
-
| Suite | Command | Needs | What it proves |
|
|
6
|
-
| -------------- | -------------------------- | -------------------------- | ----------------------------------------------- |
|
|
7
|
-
| `tests/` | `npm test` | nothing but `node_modules` | Every layer, against in-memory databases. |
|
|
8
|
-
| `integration/` | `npm run test:integration` | the `sqlite3` shell | Two-way interoperability with the native shell. |
|
|
9
|
-
|
|
10
|
-
The unit suite uses `better-sqlite3` in-process, so it needs no server, no network and no
|
|
11
|
-
binary beyond `node_modules`. The integration suite runs the real shell.
|
|
12
|
-
|
|
13
|
-
## Running the integration suite
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
sudo apt-get install sqlite3 # or brew install sqlite
|
|
17
|
-
npm run test:integration
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
- When the shell is not installed the suites skip themselves with a message.
|
|
21
|
-
`SQLITE_TEST_REQUIRED=1` (set in CI) turns that into a hard failure, so the suite can
|
|
22
|
-
never silently no-op where it was supposed to run.
|
|
23
|
-
- `SQLITE3_BIN=/path/to/sqlite3` tests a specific shell.
|
|
24
|
-
- `KEEP_TEST_OUTPUT=1` keeps every dump and database under `test-output/` for inspection;
|
|
25
|
-
CI uploads that directory when a run fails.
|
|
26
|
-
|
|
27
|
-
## What is tested
|
|
28
|
-
|
|
29
|
-
### Byte identity (`interop.integration.test.ts`)
|
|
30
|
-
|
|
31
|
-
The fixture is dumped by this package and by the shell, with each native switch and its
|
|
32
|
-
equivalent option (`.dump`, `--data-only`, `--preserve-rowids`, `--newlines`, `--nosys`).
|
|
33
|
-
Every line must be identical; a line may differ only in the digits of a `REAL` literal, and
|
|
34
|
-
is then compared again with each literal parsed as a double.
|
|
35
|
-
|
|
36
|
-
### The interoperability matrix
|
|
37
|
-
|
|
38
|
-
| Path |
|
|
39
|
-
| ------------------------------------------ |
|
|
40
|
-
| this package → native `sqlite3` restore |
|
|
41
|
-
| native `.dump` → this package's restore |
|
|
42
|
-
| this package → this package |
|
|
43
|
-
| native `.dump` → native restore (baseline) |
|
|
44
|
-
|
|
45
|
-
Each ends by snapshotting the restored database — every `sqlite_schema` row except
|
|
46
|
-
`rootpage`, and every row of every table with text compared as bytes — and deep-comparing
|
|
47
|
-
it with the source. The baseline proves the fixture itself round-trips natively, so a
|
|
48
|
-
failure elsewhere is this package's.
|
|
49
|
-
|
|
50
|
-
On top of the matrix: dumping the restored database reproduces the first dump byte for byte,
|
|
51
|
-
restored virtual tables answer FTS5 and R-tree queries, `NUL` characters survive through
|
|
52
|
-
both restores, `user_version` is carried on request, and a data-only dump loads into an
|
|
53
|
-
existing schema with `transaction: 'wrap'`.
|
|
54
|
-
|
|
55
|
-
### Scripts beyond `.dump` (`scripts.integration.test.ts`)
|
|
56
|
-
|
|
57
|
-
Hand-written scripts in shapes the shell accepts — trigger bodies with `;`, `END` and
|
|
58
|
-
`CASE`; comments everywhere; `GO` and `/` terminator lines; CRLF files and carriage returns
|
|
59
|
-
inside literals; `#` comments and dot-commands; every kind of quoted identifier; several
|
|
60
|
-
statements on a line. Each runs through the shell and through this package, and the two
|
|
61
|
-
databases must be identical — including where both stop on the same error.
|
|
62
|
-
|
|
63
|
-
### Streaming (`streaming.integration.test.ts`)
|
|
64
|
-
|
|
65
|
-
A 200 000-row table (over 30 MB of SQL) is dumped with bounded heap growth, compared with
|
|
66
|
-
the shell's `.dump`, and restored both ways.
|
|
67
|
-
|
|
68
|
-
## The fixture
|
|
69
|
-
|
|
70
|
-
`integration/fixture/schema.ts`: one statement per array element, executed one at a time
|
|
71
|
-
with `better-sqlite3` — **never through this package's parser**, so a statement-splitting
|
|
72
|
-
bug cannot corrupt the fixture and hide itself.
|
|
73
|
-
|
|
74
|
-
It is deliberately unfriendly: a view created before the table it reads; two tables
|
|
75
|
-
referencing each other; deleted rows leaving gaps in hidden rowids; names that are
|
|
76
|
-
keywords, contain spaces and quotes, or are quoted with `'`, `"` and `[…]`; `WITHOUT ROWID`,
|
|
77
|
-
`STRICT` and generated columns; partial, expression and `DESC` indexes; triggers whose
|
|
78
|
-
bodies contain `; END`; an FTS5 and an R-tree table; statistics from `ANALYZE`; 64-bit
|
|
79
|
-
extremes, infinities, `REAL`s without short representations, empty blobs, text with CRLF,
|
|
80
|
-
emoji, and bytes that are not valid UTF-8.
|
|
81
|
-
|
|
82
|
-
`sqlite_stat4` is dropped after `ANALYZE`: `better-sqlite3` builds with STAT4 and the
|
|
83
|
-
Ubuntu shell does not, and a dump carrying it cannot restore there — natively either (see
|
|
84
|
-
[known-limitations.md](known-limitations.md#sqlite_stat4-across-builds)).
|
|
1
|
+
# Round-trip testing
|
|
2
|
+
|
|
3
|
+
Two suites, kept apart on purpose.
|
|
4
|
+
|
|
5
|
+
| Suite | Command | Needs | What it proves |
|
|
6
|
+
| -------------- | -------------------------- | -------------------------- | ----------------------------------------------- |
|
|
7
|
+
| `tests/` | `npm test` | nothing but `node_modules` | Every layer, against in-memory databases. |
|
|
8
|
+
| `integration/` | `npm run test:integration` | the `sqlite3` shell | Two-way interoperability with the native shell. |
|
|
9
|
+
|
|
10
|
+
The unit suite uses `better-sqlite3` in-process, so it needs no server, no network and no
|
|
11
|
+
binary beyond `node_modules`. The integration suite runs the real shell.
|
|
12
|
+
|
|
13
|
+
## Running the integration suite
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
sudo apt-get install sqlite3 # or brew install sqlite
|
|
17
|
+
npm run test:integration
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- When the shell is not installed the suites skip themselves with a message.
|
|
21
|
+
`SQLITE_TEST_REQUIRED=1` (set in CI) turns that into a hard failure, so the suite can
|
|
22
|
+
never silently no-op where it was supposed to run.
|
|
23
|
+
- `SQLITE3_BIN=/path/to/sqlite3` tests a specific shell.
|
|
24
|
+
- `KEEP_TEST_OUTPUT=1` keeps every dump and database under `test-output/` for inspection;
|
|
25
|
+
CI uploads that directory when a run fails.
|
|
26
|
+
|
|
27
|
+
## What is tested
|
|
28
|
+
|
|
29
|
+
### Byte identity (`interop.integration.test.ts`)
|
|
30
|
+
|
|
31
|
+
The fixture is dumped by this package and by the shell, with each native switch and its
|
|
32
|
+
equivalent option (`.dump`, `--data-only`, `--preserve-rowids`, `--newlines`, `--nosys`).
|
|
33
|
+
Every line must be identical; a line may differ only in the digits of a `REAL` literal, and
|
|
34
|
+
is then compared again with each literal parsed as a double.
|
|
35
|
+
|
|
36
|
+
### The interoperability matrix
|
|
37
|
+
|
|
38
|
+
| Path |
|
|
39
|
+
| ------------------------------------------ |
|
|
40
|
+
| this package → native `sqlite3` restore |
|
|
41
|
+
| native `.dump` → this package's restore |
|
|
42
|
+
| this package → this package |
|
|
43
|
+
| native `.dump` → native restore (baseline) |
|
|
44
|
+
|
|
45
|
+
Each ends by snapshotting the restored database — every `sqlite_schema` row except
|
|
46
|
+
`rootpage`, and every row of every table with text compared as bytes — and deep-comparing
|
|
47
|
+
it with the source. The baseline proves the fixture itself round-trips natively, so a
|
|
48
|
+
failure elsewhere is this package's.
|
|
49
|
+
|
|
50
|
+
On top of the matrix: dumping the restored database reproduces the first dump byte for byte,
|
|
51
|
+
restored virtual tables answer FTS5 and R-tree queries, `NUL` characters survive through
|
|
52
|
+
both restores, `user_version` is carried on request, and a data-only dump loads into an
|
|
53
|
+
existing schema with `transaction: 'wrap'`.
|
|
54
|
+
|
|
55
|
+
### Scripts beyond `.dump` (`scripts.integration.test.ts`)
|
|
56
|
+
|
|
57
|
+
Hand-written scripts in shapes the shell accepts — trigger bodies with `;`, `END` and
|
|
58
|
+
`CASE`; comments everywhere; `GO` and `/` terminator lines; CRLF files and carriage returns
|
|
59
|
+
inside literals; `#` comments and dot-commands; every kind of quoted identifier; several
|
|
60
|
+
statements on a line. Each runs through the shell and through this package, and the two
|
|
61
|
+
databases must be identical — including where both stop on the same error.
|
|
62
|
+
|
|
63
|
+
### Streaming (`streaming.integration.test.ts`)
|
|
64
|
+
|
|
65
|
+
A 200 000-row table (over 30 MB of SQL) is dumped with bounded heap growth, compared with
|
|
66
|
+
the shell's `.dump`, and restored both ways.
|
|
67
|
+
|
|
68
|
+
## The fixture
|
|
69
|
+
|
|
70
|
+
`integration/fixture/schema.ts`: one statement per array element, executed one at a time
|
|
71
|
+
with `better-sqlite3` — **never through this package's parser**, so a statement-splitting
|
|
72
|
+
bug cannot corrupt the fixture and hide itself.
|
|
73
|
+
|
|
74
|
+
It is deliberately unfriendly: a view created before the table it reads; two tables
|
|
75
|
+
referencing each other; deleted rows leaving gaps in hidden rowids; names that are
|
|
76
|
+
keywords, contain spaces and quotes, or are quoted with `'`, `"` and `[…]`; `WITHOUT ROWID`,
|
|
77
|
+
`STRICT` and generated columns; partial, expression and `DESC` indexes; triggers whose
|
|
78
|
+
bodies contain `; END`; an FTS5 and an R-tree table; statistics from `ANALYZE`; 64-bit
|
|
79
|
+
extremes, infinities, `REAL`s without short representations, empty blobs, text with CRLF,
|
|
80
|
+
emoji, and bytes that are not valid UTF-8.
|
|
81
|
+
|
|
82
|
+
`sqlite_stat4` is dropped after `ANALYZE`: `better-sqlite3` builds with STAT4 and the
|
|
83
|
+
Ubuntu shell does not, and a dump carrying it cannot restore there — natively either (see
|
|
84
|
+
[known-limitations.md](known-limitations.md#sqlite_stat4-across-builds)).
|