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
|
@@ -29,7 +29,7 @@ interface SqliteQuery {
|
|
|
29
29
|
* `data/valueQuery.ts`), so a 64-bit integer or a `REAL`'s exact digits
|
|
30
30
|
* never pass through a JavaScript number on the way into the dump.
|
|
31
31
|
*/
|
|
32
|
-
type SqliteColumnValue = string | number | bigint | Buffer | Uint8Array | null;
|
|
32
|
+
type SqliteColumnValue = string | number | bigint | Buffer | Uint8Array | ArrayBuffer | readonly number[] | null;
|
|
33
33
|
/** A single result row, keyed by column name. */
|
|
34
34
|
interface SqliteRow {
|
|
35
35
|
readonly [column: string]: SqliteColumnValue;
|
|
@@ -66,6 +66,62 @@ interface SqliteErrorInfo {
|
|
|
66
66
|
readonly errno?: number;
|
|
67
67
|
readonly message: string;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* What a connection can and cannot do, for engines that speak SQLite's SQL
|
|
71
|
+
* but are not an embedded SQLite handle — Cloudflare D1, reached over HTTP,
|
|
72
|
+
* is the case this exists for. Every field is optional and defaults to what
|
|
73
|
+
* an ordinary SQLite handle does, so a local adapter declares nothing.
|
|
74
|
+
*
|
|
75
|
+
* The dump honours each restriction by choosing a different but equivalent
|
|
76
|
+
* query, so the output is the same dump an ordinary handle onto the same
|
|
77
|
+
* database would produce.
|
|
78
|
+
*/
|
|
79
|
+
interface SqliteConnectionFeatures {
|
|
80
|
+
/**
|
|
81
|
+
* `false` when the engine accepts no `BEGIN` / `SAVEPOINT`. A dump then
|
|
82
|
+
* reads without a snapshot (`consistency: 'none'`) and reports
|
|
83
|
+
* `snapshot-unavailable`. Defaults to `true`.
|
|
84
|
+
*/
|
|
85
|
+
readonly transactions?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* `false` when the table-valued `pragma_xxx()` functions are refused; the
|
|
88
|
+
* catalog is then read through the `PRAGMA xxx(arg)` statements instead.
|
|
89
|
+
* Defaults to `true` (for SQLite 3.16+).
|
|
90
|
+
*/
|
|
91
|
+
readonly pragmaFunctions?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* `false` when only the classic introspection pragmas are allowed:
|
|
94
|
+
* `table_info` / `index_info` in place of `table_xinfo` / `index_xinfo`,
|
|
95
|
+
* and no `table_list`. The dump is the same; what is lost is detail only a
|
|
96
|
+
* diagnostic uses (generated columns, index key order and collations).
|
|
97
|
+
* Defaults to `true`.
|
|
98
|
+
*/
|
|
99
|
+
readonly extendedPragmas?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* `false` when statements must not name a schema (`"main"."t"`). Only the
|
|
102
|
+
* `main` schema can be dumped then. Defaults to `true`.
|
|
103
|
+
*/
|
|
104
|
+
readonly schemaQualifiedNames?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* How binary values cross the connection. `'hex'` has SQLite return them
|
|
107
|
+
* as `hex()` text, for transports — such as JSON — that cannot carry bytes
|
|
108
|
+
* exactly. Defaults to `'native'`.
|
|
109
|
+
*/
|
|
110
|
+
readonly binaryTransport?: 'native' | 'hex';
|
|
111
|
+
/**
|
|
112
|
+
* Read table data in pages of this many rows through {@link
|
|
113
|
+
* SqliteConnection.query} instead of {@link SqliteConnection.stream}, for
|
|
114
|
+
* engines that return a whole result at once. Pages are keyed on the rowid
|
|
115
|
+
* or the primary key, so each one is an index seek.
|
|
116
|
+
*/
|
|
117
|
+
readonly pagedReadSize?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Name prefixes of objects the engine keeps for itself in `sqlite_schema`
|
|
120
|
+
* and refuses to let a client read — D1's `_cf_` tables. They are left out
|
|
121
|
+
* of the dump. Compared case-insensitively.
|
|
122
|
+
*/
|
|
123
|
+
readonly reservedNamePrefixes?: readonly string[];
|
|
124
|
+
}
|
|
69
125
|
/**
|
|
70
126
|
* One open SQLite database handle.
|
|
71
127
|
*
|
|
@@ -76,7 +132,16 @@ interface SqliteErrorInfo {
|
|
|
76
132
|
* stepping.
|
|
77
133
|
*/
|
|
78
134
|
interface SqliteConnection {
|
|
135
|
+
/** Restrictions of the engine behind this handle; see {@link SqliteConnectionFeatures}. */
|
|
136
|
+
readonly features?: SqliteConnectionFeatures;
|
|
79
137
|
query<Row extends SqliteRow = SqliteRow>(query: SqliteQuery, signal?: AbortSignal): Promise<SqliteQueryResult<Row>>;
|
|
138
|
+
/**
|
|
139
|
+
* Runs several read-only queries in one round trip, returning one result
|
|
140
|
+
* per query in order. Optional: introspection uses it, when present, to
|
|
141
|
+
* fetch the catalog of every table in a few requests instead of several
|
|
142
|
+
* per table. If a batch fails as a whole, its queries are run one by one.
|
|
143
|
+
*/
|
|
144
|
+
queryBatch?(queries: readonly SqliteQuery[], signal?: AbortSignal): Promise<readonly SqliteQueryResult[]>;
|
|
80
145
|
/** Streams rows without buffering the full result set in memory. */
|
|
81
146
|
stream<Row extends SqliteRow = SqliteRow>(query: SqliteQuery, options?: SqliteStreamOptions): AsyncIterable<Row>;
|
|
82
147
|
/**
|
|
@@ -148,4 +213,4 @@ interface SqliteConnectionSource {
|
|
|
148
213
|
type SqliteConnectionInput = SqliteConnection | SqliteConnectionSource;
|
|
149
214
|
declare function isSqliteConnectionSource(input: SqliteConnectionInput): input is SqliteConnectionSource;
|
|
150
215
|
|
|
151
|
-
export { type AcquiredSqliteConnection as A, type SqliteConnection as S, type
|
|
216
|
+
export { type AcquiredSqliteConnection as A, type SqliteConnection as S, type SqliteConnectionFeatures as a, type SqliteConnectionInput as b, type SqliteExecResult as c, type SqliteRow as d, type SqliteErrorInfo as e, type SqliteColumnValue as f, type SqliteConnectionSource as g, type SqliteParameterValue as h, type SqliteQuery as i, type SqliteQueryResult as j, type SqliteStreamOptions as k, isSqliteConnectionSource as l };
|
|
@@ -29,7 +29,7 @@ interface SqliteQuery {
|
|
|
29
29
|
* `data/valueQuery.ts`), so a 64-bit integer or a `REAL`'s exact digits
|
|
30
30
|
* never pass through a JavaScript number on the way into the dump.
|
|
31
31
|
*/
|
|
32
|
-
type SqliteColumnValue = string | number | bigint | Buffer | Uint8Array | null;
|
|
32
|
+
type SqliteColumnValue = string | number | bigint | Buffer | Uint8Array | ArrayBuffer | readonly number[] | null;
|
|
33
33
|
/** A single result row, keyed by column name. */
|
|
34
34
|
interface SqliteRow {
|
|
35
35
|
readonly [column: string]: SqliteColumnValue;
|
|
@@ -66,6 +66,62 @@ interface SqliteErrorInfo {
|
|
|
66
66
|
readonly errno?: number;
|
|
67
67
|
readonly message: string;
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* What a connection can and cannot do, for engines that speak SQLite's SQL
|
|
71
|
+
* but are not an embedded SQLite handle — Cloudflare D1, reached over HTTP,
|
|
72
|
+
* is the case this exists for. Every field is optional and defaults to what
|
|
73
|
+
* an ordinary SQLite handle does, so a local adapter declares nothing.
|
|
74
|
+
*
|
|
75
|
+
* The dump honours each restriction by choosing a different but equivalent
|
|
76
|
+
* query, so the output is the same dump an ordinary handle onto the same
|
|
77
|
+
* database would produce.
|
|
78
|
+
*/
|
|
79
|
+
interface SqliteConnectionFeatures {
|
|
80
|
+
/**
|
|
81
|
+
* `false` when the engine accepts no `BEGIN` / `SAVEPOINT`. A dump then
|
|
82
|
+
* reads without a snapshot (`consistency: 'none'`) and reports
|
|
83
|
+
* `snapshot-unavailable`. Defaults to `true`.
|
|
84
|
+
*/
|
|
85
|
+
readonly transactions?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* `false` when the table-valued `pragma_xxx()` functions are refused; the
|
|
88
|
+
* catalog is then read through the `PRAGMA xxx(arg)` statements instead.
|
|
89
|
+
* Defaults to `true` (for SQLite 3.16+).
|
|
90
|
+
*/
|
|
91
|
+
readonly pragmaFunctions?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* `false` when only the classic introspection pragmas are allowed:
|
|
94
|
+
* `table_info` / `index_info` in place of `table_xinfo` / `index_xinfo`,
|
|
95
|
+
* and no `table_list`. The dump is the same; what is lost is detail only a
|
|
96
|
+
* diagnostic uses (generated columns, index key order and collations).
|
|
97
|
+
* Defaults to `true`.
|
|
98
|
+
*/
|
|
99
|
+
readonly extendedPragmas?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* `false` when statements must not name a schema (`"main"."t"`). Only the
|
|
102
|
+
* `main` schema can be dumped then. Defaults to `true`.
|
|
103
|
+
*/
|
|
104
|
+
readonly schemaQualifiedNames?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* How binary values cross the connection. `'hex'` has SQLite return them
|
|
107
|
+
* as `hex()` text, for transports — such as JSON — that cannot carry bytes
|
|
108
|
+
* exactly. Defaults to `'native'`.
|
|
109
|
+
*/
|
|
110
|
+
readonly binaryTransport?: 'native' | 'hex';
|
|
111
|
+
/**
|
|
112
|
+
* Read table data in pages of this many rows through {@link
|
|
113
|
+
* SqliteConnection.query} instead of {@link SqliteConnection.stream}, for
|
|
114
|
+
* engines that return a whole result at once. Pages are keyed on the rowid
|
|
115
|
+
* or the primary key, so each one is an index seek.
|
|
116
|
+
*/
|
|
117
|
+
readonly pagedReadSize?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Name prefixes of objects the engine keeps for itself in `sqlite_schema`
|
|
120
|
+
* and refuses to let a client read — D1's `_cf_` tables. They are left out
|
|
121
|
+
* of the dump. Compared case-insensitively.
|
|
122
|
+
*/
|
|
123
|
+
readonly reservedNamePrefixes?: readonly string[];
|
|
124
|
+
}
|
|
69
125
|
/**
|
|
70
126
|
* One open SQLite database handle.
|
|
71
127
|
*
|
|
@@ -76,7 +132,16 @@ interface SqliteErrorInfo {
|
|
|
76
132
|
* stepping.
|
|
77
133
|
*/
|
|
78
134
|
interface SqliteConnection {
|
|
135
|
+
/** Restrictions of the engine behind this handle; see {@link SqliteConnectionFeatures}. */
|
|
136
|
+
readonly features?: SqliteConnectionFeatures;
|
|
79
137
|
query<Row extends SqliteRow = SqliteRow>(query: SqliteQuery, signal?: AbortSignal): Promise<SqliteQueryResult<Row>>;
|
|
138
|
+
/**
|
|
139
|
+
* Runs several read-only queries in one round trip, returning one result
|
|
140
|
+
* per query in order. Optional: introspection uses it, when present, to
|
|
141
|
+
* fetch the catalog of every table in a few requests instead of several
|
|
142
|
+
* per table. If a batch fails as a whole, its queries are run one by one.
|
|
143
|
+
*/
|
|
144
|
+
queryBatch?(queries: readonly SqliteQuery[], signal?: AbortSignal): Promise<readonly SqliteQueryResult[]>;
|
|
80
145
|
/** Streams rows without buffering the full result set in memory. */
|
|
81
146
|
stream<Row extends SqliteRow = SqliteRow>(query: SqliteQuery, options?: SqliteStreamOptions): AsyncIterable<Row>;
|
|
82
147
|
/**
|
|
@@ -148,4 +213,4 @@ interface SqliteConnectionSource {
|
|
|
148
213
|
type SqliteConnectionInput = SqliteConnection | SqliteConnectionSource;
|
|
149
214
|
declare function isSqliteConnectionSource(input: SqliteConnectionInput): input is SqliteConnectionSource;
|
|
150
215
|
|
|
151
|
-
export { type AcquiredSqliteConnection as A, type SqliteConnection as S, type
|
|
216
|
+
export { type AcquiredSqliteConnection as A, type SqliteConnection as S, type SqliteConnectionFeatures as a, type SqliteConnectionInput as b, type SqliteExecResult as c, type SqliteRow as d, type SqliteErrorInfo as e, type SqliteColumnValue as f, type SqliteConnectionSource as g, type SqliteParameterValue as h, type SqliteQuery as i, type SqliteQueryResult as j, type SqliteStreamOptions as k, isSqliteConnectionSource as l };
|
package/docs/architecture.md
CHANGED
|
@@ -1,157 +1,180 @@
|
|
|
1
|
-
# Architecture
|
|
2
|
-
|
|
3
|
-
The package is a stack of layers, each usable on its own. Nothing above the `connection`
|
|
4
|
-
layer knows what driver is in use; nothing below the `api` layer knows about a dump as a
|
|
5
|
-
whole.
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
┌──────────────────────────────────────────┐
|
|
9
|
-
api/ │ dumpSqlite() · restoreSqlDump() │ orchestration
|
|
10
|
-
└──────────────────────────────────────────┘
|
|
11
|
-
│ │ │
|
|
12
|
-
introspection/ ──────────┘ │ └────── restore/
|
|
13
|
-
sqlite_schema + pragmas → model │ sqlite3_complete + shell
|
|
14
|
-
│ line rules → statements
|
|
15
|
-
archive/ ─────── plan: what & in what order
|
|
16
|
-
renderer/ ────── model + plan → plain SQL text preflight/ compatibility/
|
|
17
|
-
data/ ────────── rows → INSERT statements selection/ security/
|
|
18
|
-
writer/ ──────── text/bytes → Writable version/ model/
|
|
19
|
-
│
|
|
20
|
-
┌──────────────────────────────────────────┐
|
|
21
|
-
connection/ │ SqliteConnection (driver-agnostic) │
|
|
22
|
-
└──────────────────────────────────────────┘
|
|
23
|
-
│
|
|
24
|
-
better-sqlite3.ts ─ the only module that knows better-sqlite3 exists
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
This mirrors `dbgate-mysql-dumper`, `dbgate-pg-dumper`, `dbgate-mssql-dumper` and
|
|
28
|
-
`dbgate-redis-dumper`; the SQLite-specific differences are called out below.
|
|
29
|
-
|
|
30
|
-
## `connection/` — the driver boundary
|
|
31
|
-
|
|
32
|
-
`SqliteConnection` is the whole contract between this package and a driver: `query()`,
|
|
33
|
-
`stream()`, and optional `execute()`, `describeError()`, `isInTransaction()` and
|
|
34
|
-
`setDefensive()`, plus `cancel()`. The core never imports a driver, and
|
|
35
|
-
`tests/packageBoundaries.test.ts` fails if it starts to.
|
|
36
|
-
|
|
37
|
-
SQLite is embedded, so a "connection" is one open database handle. The contract keeps the
|
|
38
|
-
shape of the network drivers' in the sibling packages — including `SqliteConnectionSource`
|
|
39
|
-
for pool-like inputs — so an application can drive every dumper the same way.
|
|
40
|
-
|
|
41
|
-
###
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
`
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
The package is a stack of layers, each usable on its own. Nothing above the `connection`
|
|
4
|
+
layer knows what driver is in use; nothing below the `api` layer knows about a dump as a
|
|
5
|
+
whole.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────┐
|
|
9
|
+
api/ │ dumpSqlite() · restoreSqlDump() │ orchestration
|
|
10
|
+
└──────────────────────────────────────────┘
|
|
11
|
+
│ │ │
|
|
12
|
+
introspection/ ──────────┘ │ └────── restore/
|
|
13
|
+
sqlite_schema + pragmas → model │ sqlite3_complete + shell
|
|
14
|
+
│ line rules → statements
|
|
15
|
+
archive/ ─────── plan: what & in what order
|
|
16
|
+
renderer/ ────── model + plan → plain SQL text preflight/ compatibility/
|
|
17
|
+
data/ ────────── rows → INSERT statements selection/ security/
|
|
18
|
+
writer/ ──────── text/bytes → Writable version/ model/
|
|
19
|
+
│
|
|
20
|
+
┌──────────────────────────────────────────┐
|
|
21
|
+
connection/ │ SqliteConnection (driver-agnostic) │
|
|
22
|
+
└──────────────────────────────────────────┘
|
|
23
|
+
│
|
|
24
|
+
better-sqlite3.ts ─ the only module that knows better-sqlite3 exists
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This mirrors `dbgate-mysql-dumper`, `dbgate-pg-dumper`, `dbgate-mssql-dumper` and
|
|
28
|
+
`dbgate-redis-dumper`; the SQLite-specific differences are called out below.
|
|
29
|
+
|
|
30
|
+
## `connection/` — the driver boundary
|
|
31
|
+
|
|
32
|
+
`SqliteConnection` is the whole contract between this package and a driver: `query()`,
|
|
33
|
+
`stream()`, and optional `execute()`, `describeError()`, `isInTransaction()` and
|
|
34
|
+
`setDefensive()`, plus `cancel()`. The core never imports a driver, and
|
|
35
|
+
`tests/packageBoundaries.test.ts` fails if it starts to.
|
|
36
|
+
|
|
37
|
+
SQLite is embedded, so a "connection" is one open database handle. The contract keeps the
|
|
38
|
+
shape of the network drivers' in the sibling packages — including `SqliteConnectionSource`
|
|
39
|
+
for pool-like inputs — so an application can drive every dumper the same way.
|
|
40
|
+
|
|
41
|
+
### Connection features: engines that are not a SQLite handle
|
|
42
|
+
|
|
43
|
+
An adapter may declare `features` — restrictions of the engine behind it — and an optional
|
|
44
|
+
`queryBatch()`. Cloudflare D1 (`src/d1.ts`) is why they exist: SQLite's SQL, reached over
|
|
45
|
+
HTTP, behind an authorizer. Each feature makes the core choose an equivalent query rather
|
|
46
|
+
than a different dump:
|
|
47
|
+
|
|
48
|
+
- `transactions: false` — the session reads without a snapshot and says so
|
|
49
|
+
(`snapshot-unavailable`);
|
|
50
|
+
- `pragmaFunctions: false` / `extendedPragmas: false` — the catalog is read through the
|
|
51
|
+
classic `PRAGMA` statements (the reader also falls back on its own the first time an
|
|
52
|
+
extended pragma is refused);
|
|
53
|
+
- `schemaQualifiedNames: false` — no statement names a schema;
|
|
54
|
+
- `binaryTransport: 'hex'` — text and blob bytes are selected as `hex()`;
|
|
55
|
+
- `pagedReadSize` — table data is read through `query()` in pages keyed on the rowid or
|
|
56
|
+
the primary key, in natural scan order, instead of through `stream()`;
|
|
57
|
+
- `reservedNamePrefixes` — objects the engine keeps for itself are left out, together
|
|
58
|
+
with their counters and statistics.
|
|
59
|
+
|
|
60
|
+
`queryBatch()` lets introspection fetch every table's catalog in a few requests; a batch
|
|
61
|
+
that fails is not fatal, its queries simply run one by one. The tests run every feature
|
|
62
|
+
against an ordinary handle as well, where each must leave the dump byte-identical.
|
|
63
|
+
|
|
64
|
+
### Why one read transaction
|
|
65
|
+
|
|
66
|
+
A dump runs entirely inside one transaction on one handle (`session.ts`), opened as a
|
|
67
|
+
`SAVEPOINT` so that it nests inside a transaction the caller already has, and with one read
|
|
68
|
+
issued immediately so the snapshot is taken at the start rather than by whichever catalog
|
|
69
|
+
query happens to run first. In WAL mode that is a true snapshot; in rollback-journal mode it
|
|
70
|
+
holds a `SHARED` lock. The native shell does the same (`SAVEPOINT dump`). It is released
|
|
71
|
+
without the caller's `AbortSignal`, so a cancelled dump still ends it.
|
|
72
|
+
|
|
73
|
+
## `introspection/` — catalog to model
|
|
74
|
+
|
|
75
|
+
Everything comes from `sqlite_schema` (read as `sqlite_master`, which every release
|
|
76
|
+
accepts), in rowid order, plus the table-valued pragma functions — `table_xinfo`,
|
|
77
|
+
`index_list`, `index_xinfo`, `foreign_key_list`, `table_list` — with a fallback to plain
|
|
78
|
+
`PRAGMA` statements on libraries too old for them. Tables are classified as ordinary,
|
|
79
|
+
virtual, shadow (a virtual table's storage) or system (`sqlite_*`).
|
|
80
|
+
|
|
81
|
+
### Why the DDL is not reconstructed
|
|
82
|
+
|
|
83
|
+
SQLite keeps every `CREATE` statement verbatim, and the native `.dump` writes that text.
|
|
84
|
+
Reconstructing DDL from the column model would lose comments, spelling, formatting and any
|
|
85
|
+
clause the model does not know about, for no gain. The model exists for everything else —
|
|
86
|
+
planning, selection, `INSERT` column lists, compatibility checks, diagnostics — which is why
|
|
87
|
+
`renderPlainSql` is a pure function of it.
|
|
88
|
+
|
|
89
|
+
## `archive/` — the plan
|
|
90
|
+
|
|
91
|
+
`inspectDumpArchive` turns a model into ordered `ArchiveEntry` objects. It is pure: no SQL
|
|
92
|
+
text, no streams, no connection.
|
|
93
|
+
|
|
94
|
+
The order is the native `.dump`'s — creation order — not a topological sort. Creation order
|
|
95
|
+
is valid for everything SQLite checks at `CREATE` time (an index after its table, a trigger
|
|
96
|
+
after its table or view), SQLite resolves view and trigger bodies lazily, and the dump's
|
|
97
|
+
`PRAGMA foreign_keys=OFF` makes table order irrelevant to foreign keys. Dependencies are
|
|
98
|
+
still recorded and then _verified_ against that order:
|
|
99
|
+
|
|
100
|
+
- **`hard`** — a trigger depends on its table's data this way: created before the rows, it
|
|
101
|
+
would fire for each one.
|
|
102
|
+
- **`preference`** — every foreign key. Recording them as hard edges would report a false
|
|
103
|
+
cycle for exactly the circular schemas that restore perfectly well.
|
|
104
|
+
|
|
105
|
+
## `renderer/` — model to text
|
|
106
|
+
|
|
107
|
+
A port of the parts of `shell.c` that shape `.dump` output — `printSchemaLine()`,
|
|
108
|
+
`run_table_dump_query()`, the virtual-table `INSERT INTO sqlite_schema` — driven by the
|
|
109
|
+
archive, with row data arriving through an `onTableData` hook so the renderer never needs a
|
|
110
|
+
connection. `sqlite3_complete()` itself is ported (in `restore/complete.ts`) because
|
|
111
|
+
`printSchemaLine()` calls it to decide how to terminate DDL that ends in a comment.
|
|
112
|
+
|
|
113
|
+
## `data/` — rows to `INSERT`
|
|
114
|
+
|
|
115
|
+
`exportTableDataAsInserts` streams one table in constant memory.
|
|
116
|
+
|
|
117
|
+
### Why SQLite renders the values
|
|
118
|
+
|
|
119
|
+
The `SELECT` it runs does not fetch column values; it fetches each value's storage class and
|
|
120
|
+
a representation computed _by SQLite_: integers as text, reals as the finished literal —
|
|
121
|
+
using SQLite's own `printf('%!.20g')`, the formatter the shell uses — and text as its stored
|
|
122
|
+
bytes. Three problems disappear at once: 64-bit integers never become JavaScript numbers;
|
|
123
|
+
`REAL` digits match the shell's exactly, which no JavaScript formatting could; and text that
|
|
124
|
+
is not valid UTF-8 is not "repaired" by the driver's decoder. The core is then exact with
|
|
125
|
+
any driver that can return strings and bytes.
|
|
126
|
+
|
|
127
|
+
`SqlChunkBuilder` keeps `(string | Buffer)[]` parts and only falls back to `Buffer.concat`
|
|
128
|
+
once it has been handed bytes, so the all-text case stays on the string fast path.
|
|
129
|
+
|
|
130
|
+
## `restore/` — text to statements
|
|
131
|
+
|
|
132
|
+
### The parser
|
|
133
|
+
|
|
134
|
+
`SqlStatementParser` is an incremental scanner over **bytes**: a dump is not necessarily
|
|
135
|
+
valid UTF-8, and `latin1` is a bijection between bytes and code units, so nothing is lost
|
|
136
|
+
and every character the scanner reacts to is ASCII.
|
|
137
|
+
|
|
138
|
+
It has two layers, both ported: `sqlite3_complete()`'s eight-state machine, which decides
|
|
139
|
+
when a `;` completes a statement (and keeps trigger bodies whole), and the shell's
|
|
140
|
+
`process_input()` line rules — dot-commands and `#` comments at column 0 when nothing is
|
|
141
|
+
pending, `GO`/`/` terminator lines checked with the shell's own `line_is_complete()`
|
|
142
|
+
condition, and the `\r` its line reader drops before each `\n`.
|
|
143
|
+
|
|
144
|
+
Anything whose meaning depends on the next chunk — a `-` or `/` that may open a comment, a
|
|
145
|
+
`*` that may close one, a `\r` that may precede `\n`, a line start that may be a `GO` line,
|
|
146
|
+
a word that may be a keyword — is carried or kept in state, never guessed.
|
|
147
|
+
`tests/statementParser.test.ts` checks identical output at every chunk size and every split
|
|
148
|
+
point.
|
|
149
|
+
|
|
150
|
+
### Session cleanup
|
|
151
|
+
|
|
152
|
+
`RestoreSessionState` follows only classified top-level statements (transaction control,
|
|
153
|
+
`PRAGMA foreign_keys`, `PRAGMA writable_schema`, writes to the schema table), never text
|
|
154
|
+
inside rows or trigger bodies, and puts back what the script changed: it rolls back a
|
|
155
|
+
transaction the script opened and left open, resets `writable_schema`, and returns
|
|
156
|
+
`foreign_keys` to its previous value — which a native dump never does, because it is written
|
|
157
|
+
for a shell that exits.
|
|
158
|
+
|
|
159
|
+
## `writer/` — text and bytes out
|
|
160
|
+
|
|
161
|
+
`DumpWriter.write` accepts `string | Buffer` for the reason above. `StreamDumpWriter` honours
|
|
162
|
+
backpressure by gating on `write()`'s return value with the `drain` listener attached in the
|
|
163
|
+
same tick, and never calls `end()` on a caller-owned stream.
|
|
164
|
+
|
|
165
|
+
## `security/` — quoting and escaping
|
|
166
|
+
|
|
167
|
+
Two quoting functions, for two jobs. `quoteIdentifier` always double-quotes, and is used for
|
|
168
|
+
every query this package runs. `quoteIdentifierIfNeeded` reproduces the shell's
|
|
169
|
+
`quoteChar()` — quote only non-identifiers and SQLite's 147 keywords — and is used only
|
|
170
|
+
where output must match the native layout. Literal rendering ports
|
|
171
|
+
`output_quoted_escaped_string()`, including `unused_string()`'s placeholder choice.
|
|
172
|
+
|
|
173
|
+
## Testing strategy
|
|
174
|
+
|
|
175
|
+
| Suite | Needs | What it proves |
|
|
176
|
+
| -------------- | --------------- | ------------------------------------------------------------------- |
|
|
177
|
+
| `tests/` | nothing | Every layer, against in-memory `better-sqlite3` databases. |
|
|
178
|
+
| `integration/` | `sqlite3` shell | Byte identity, the four-way matrix, shell-script parity, streaming. |
|
|
179
|
+
|
|
180
|
+
See [round-trip-testing.md](round-trip-testing.md).
|