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/dist/index.js
CHANGED
|
@@ -41,19 +41,46 @@ function toText(value) {
|
|
|
41
41
|
if (typeof value === "string") {
|
|
42
42
|
return value;
|
|
43
43
|
}
|
|
44
|
-
if (value instanceof Uint8Array) {
|
|
45
|
-
return
|
|
44
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer || Array.isArray(value)) {
|
|
45
|
+
return toBuffer(value).toString("utf8");
|
|
46
46
|
}
|
|
47
47
|
return String(value);
|
|
48
48
|
}
|
|
49
|
+
function toBuffer(value) {
|
|
50
|
+
if (Buffer.isBuffer(value)) {
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
if (value instanceof Uint8Array) {
|
|
54
|
+
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
|
55
|
+
}
|
|
56
|
+
if (value instanceof ArrayBuffer) {
|
|
57
|
+
return Buffer.from(value);
|
|
58
|
+
}
|
|
59
|
+
return Buffer.from(value);
|
|
60
|
+
}
|
|
61
|
+
function connectionFeatures(connection) {
|
|
62
|
+
const features = connection.features ?? {};
|
|
63
|
+
const pageSize = features.pagedReadSize;
|
|
64
|
+
return {
|
|
65
|
+
transactions: features.transactions ?? true,
|
|
66
|
+
pragmaFunctions: features.pragmaFunctions ?? true,
|
|
67
|
+
extendedPragmas: features.extendedPragmas ?? true,
|
|
68
|
+
schemaQualifiedNames: features.schemaQualifiedNames ?? true,
|
|
69
|
+
binaryTransport: features.binaryTransport ?? "native",
|
|
70
|
+
pagedReadSize: pageSize === void 0 || !Number.isFinite(pageSize) ? void 0 : Math.max(1, Math.floor(pageSize)),
|
|
71
|
+
reservedNamePrefixes: features.reservedNamePrefixes ?? []
|
|
72
|
+
};
|
|
73
|
+
}
|
|
49
74
|
|
|
50
75
|
// src/connection/session.ts
|
|
51
76
|
var DUMP_SAVEPOINT = "dbgate_sqlite_dump";
|
|
52
77
|
async function beginSqliteDumpSession(connection, options, signal) {
|
|
53
|
-
const
|
|
78
|
+
const requested = options?.consistency ?? "snapshot";
|
|
54
79
|
const joinedExistingTransaction = connection.isInTransaction?.() ?? false;
|
|
80
|
+
const snapshotUnavailable = requested === "snapshot" && !connectionFeatures(connection).transactions;
|
|
81
|
+
const consistency = snapshotUnavailable ? "none" : requested;
|
|
55
82
|
if (consistency === "none") {
|
|
56
|
-
return { consistency, joinedExistingTransaction, finish: async () => {
|
|
83
|
+
return { consistency, joinedExistingTransaction, snapshotUnavailable, finish: async () => {
|
|
57
84
|
} };
|
|
58
85
|
}
|
|
59
86
|
await executeStatement(connection, `SAVEPOINT ${DUMP_SAVEPOINT}`, signal);
|
|
@@ -72,7 +99,7 @@ async function beginSqliteDumpSession(connection, options, signal) {
|
|
|
72
99
|
await finish();
|
|
73
100
|
throw error;
|
|
74
101
|
}
|
|
75
|
-
return { consistency, joinedExistingTransaction, finish };
|
|
102
|
+
return { consistency, joinedExistingTransaction, snapshotUnavailable: false, finish };
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
// src/version/types.ts
|
|
@@ -898,17 +925,34 @@ async function introspectSqlite(connection, options, signal) {
|
|
|
898
925
|
const schemaName = options?.schemaName ?? "main";
|
|
899
926
|
const version = await detectSqliteVersion(connection, signal);
|
|
900
927
|
const capabilities = detectSqliteCapabilities(version);
|
|
928
|
+
const features = connectionFeatures(connection);
|
|
901
929
|
const diagnostics = [];
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
930
|
+
if (!features.schemaQualifiedNames && toAsciiLowerCase(schemaName) !== "main") {
|
|
931
|
+
throw new SqliteDumperError(
|
|
932
|
+
"schema-not-found",
|
|
933
|
+
`This connection can only read the main database, not ${JSON.stringify(schemaName)}`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const catalog = new CatalogReader(connection, schemaName, capabilities, features, signal);
|
|
937
|
+
const allSchemaRows = await catalog.schemaRows();
|
|
938
|
+
if (allSchemaRows === null) {
|
|
905
939
|
throw new SqliteDumperError(
|
|
906
940
|
"schema-not-found",
|
|
907
941
|
`No database named ${JSON.stringify(schemaName)} is attached to this connection`
|
|
908
942
|
);
|
|
909
943
|
}
|
|
944
|
+
const reserved = allSchemaRows.filter((row) => isReservedRow(row, features.reservedNamePrefixes));
|
|
945
|
+
const schemaRows = allSchemaRows.filter((row) => !reserved.includes(row));
|
|
946
|
+
if (reserved.length > 0) {
|
|
947
|
+
diagnostics.push({
|
|
948
|
+
severity: "info",
|
|
949
|
+
code: "reserved-objects-skipped",
|
|
950
|
+
message: `Left out ${reserved.length} object${reserved.length === 1 ? "" : "s"} the database engine reserves for itself and does not let clients read: ${reserved.map((row) => JSON.stringify(row.name)).join(", ")}.`
|
|
951
|
+
});
|
|
952
|
+
}
|
|
910
953
|
const tableList = await catalog.tableList();
|
|
911
954
|
const virtualTableNames = schemaRows.filter((row) => row.type === "table" && isVirtualTableSql(row.sql)).map((row) => row.name);
|
|
955
|
+
await catalog.prefetch(schemaRows);
|
|
912
956
|
const tables = [];
|
|
913
957
|
const indexes = [];
|
|
914
958
|
const foreignKeys = [];
|
|
@@ -1009,6 +1053,20 @@ async function introspectSqlite(connection, options, signal) {
|
|
|
1009
1053
|
function errorMessage(error) {
|
|
1010
1054
|
return error instanceof Error ? error.message : String(error);
|
|
1011
1055
|
}
|
|
1056
|
+
function isReservedRow(row, prefixes) {
|
|
1057
|
+
if (prefixes.length === 0) {
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
const names = [toAsciiLowerCase(row.name), toAsciiLowerCase(row.tableName)];
|
|
1061
|
+
return prefixes.some((prefix) => {
|
|
1062
|
+
const lower = toAsciiLowerCase(prefix);
|
|
1063
|
+
return names.some((name) => name.startsWith(lower));
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
var PREFETCH_BATCH_SIZE = 50;
|
|
1067
|
+
function queryKey(query) {
|
|
1068
|
+
return `${query.sql}\0${JSON.stringify(query.parameters ?? [])}`;
|
|
1069
|
+
}
|
|
1012
1070
|
function isVirtualTableSql(sql) {
|
|
1013
1071
|
return sql !== null && sql.startsWith("CREATE VIRTUAL TABLE");
|
|
1014
1072
|
}
|
|
@@ -1052,43 +1110,142 @@ function findRowidAlias(columns, indexes) {
|
|
|
1052
1110
|
return column.name;
|
|
1053
1111
|
}
|
|
1054
1112
|
var CatalogReader = class {
|
|
1055
|
-
constructor(connection, schemaName, capabilities, signal) {
|
|
1113
|
+
constructor(connection, schemaName, capabilities, features, signal) {
|
|
1056
1114
|
this.connection = connection;
|
|
1057
1115
|
this.schemaName = schemaName;
|
|
1058
1116
|
this.capabilities = capabilities;
|
|
1117
|
+
this.features = features;
|
|
1059
1118
|
this.signal = signal;
|
|
1060
|
-
this.qualifier = quoteIdentifier(schemaName);
|
|
1119
|
+
this.qualifier = features.schemaQualifiedNames ? `${quoteIdentifier(schemaName)}.` : "";
|
|
1120
|
+
this.usePragmaFunctions = capabilities.supportsPragmaFunctions && features.pragmaFunctions;
|
|
1121
|
+
this.useTableXinfo = capabilities.supportsTableXinfo && features.extendedPragmas;
|
|
1122
|
+
this.useIndexXinfo = features.extendedPragmas;
|
|
1061
1123
|
}
|
|
1062
1124
|
connection;
|
|
1063
1125
|
schemaName;
|
|
1064
1126
|
capabilities;
|
|
1127
|
+
features;
|
|
1065
1128
|
signal;
|
|
1129
|
+
/** `"schema".`, or empty for a connection that cannot qualify names. */
|
|
1066
1130
|
qualifier;
|
|
1131
|
+
usePragmaFunctions;
|
|
1132
|
+
// Not readonly: an engine that refuses the extended pragmas outright (some
|
|
1133
|
+
// remote engines allow only the classic ones) is switched to the classic
|
|
1134
|
+
// ones the first time one of them succeeds where its extended form failed.
|
|
1135
|
+
useTableXinfo;
|
|
1136
|
+
useIndexXinfo;
|
|
1137
|
+
prefetched = /* @__PURE__ */ new Map();
|
|
1067
1138
|
async rows(sql, parameters) {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
);
|
|
1139
|
+
return this.run(parameters === void 0 ? { sql } : { sql, parameters });
|
|
1140
|
+
}
|
|
1141
|
+
async run(query) {
|
|
1142
|
+
const prefetched = this.prefetched.get(queryKey(query));
|
|
1143
|
+
if (prefetched !== void 0) {
|
|
1144
|
+
return prefetched;
|
|
1145
|
+
}
|
|
1146
|
+
const result = await this.connection.query(query, this.signal);
|
|
1072
1147
|
return result.rows;
|
|
1073
1148
|
}
|
|
1074
|
-
/**
|
|
1075
|
-
|
|
1076
|
-
if (this.
|
|
1077
|
-
return
|
|
1149
|
+
/** `PRAGMA schema.name(argument)`, as a table-valued function when the library has them. */
|
|
1150
|
+
pragmaQuery(name, argument, columns) {
|
|
1151
|
+
if (this.usePragmaFunctions) {
|
|
1152
|
+
return {
|
|
1153
|
+
sql: `SELECT ${columns} FROM pragma_${name}(?, ?)`,
|
|
1154
|
+
parameters: [argument, this.schemaName]
|
|
1155
|
+
};
|
|
1078
1156
|
}
|
|
1079
|
-
return
|
|
1157
|
+
return { sql: `PRAGMA ${this.qualifier}${name}(${quoteStringLiteral(argument)})` };
|
|
1080
1158
|
}
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
|
|
1159
|
+
columnsQuery(tableName) {
|
|
1160
|
+
const withHidden = this.useTableXinfo;
|
|
1161
|
+
return this.pragmaQuery(
|
|
1162
|
+
withHidden ? "table_xinfo" : "table_info",
|
|
1163
|
+
tableName,
|
|
1164
|
+
`cid, name, type, "notnull" AS isNotNull, dflt_value AS defaultValue, pk${withHidden ? ", hidden" : ""}`
|
|
1085
1165
|
);
|
|
1086
|
-
|
|
1087
|
-
|
|
1166
|
+
}
|
|
1167
|
+
indexListQuery(tableName) {
|
|
1168
|
+
return this.pragmaQuery(
|
|
1169
|
+
"index_list",
|
|
1170
|
+
tableName,
|
|
1171
|
+
'seq, name, "unique" AS isUnique, origin, partial'
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
indexColumnsQuery(indexName) {
|
|
1175
|
+
return this.useIndexXinfo ? this.pragmaQuery(
|
|
1176
|
+
"index_xinfo",
|
|
1177
|
+
indexName,
|
|
1178
|
+
'seqno, cid, name, "desc" AS isDesc, coll, "key" AS isKey'
|
|
1179
|
+
) : this.pragmaQuery("index_info", indexName, "seqno, cid, name");
|
|
1180
|
+
}
|
|
1181
|
+
foreignKeysQuery(tableName) {
|
|
1182
|
+
return this.pragmaQuery(
|
|
1183
|
+
"foreign_key_list",
|
|
1184
|
+
tableName,
|
|
1185
|
+
'id, seq, "table" AS referencedTable, "from" AS fromColumn, "to" AS toColumn, on_update AS onUpdate, on_delete AS onDelete, "match" AS matchMode'
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* Fetches the per-table catalog of every object up front through
|
|
1190
|
+
* {@link SqliteConnection.queryBatch}, when the connection has it — for a
|
|
1191
|
+
* remote engine, a few requests instead of several per table. A batch that
|
|
1192
|
+
* fails is simply not cached: its queries then run one by one later, where
|
|
1193
|
+
* a failing one is handled (or reported) as it would be without batching.
|
|
1194
|
+
*/
|
|
1195
|
+
async prefetch(schemaRows) {
|
|
1196
|
+
if (!this.connection.queryBatch) {
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const queries = [];
|
|
1200
|
+
for (const row of schemaRows) {
|
|
1201
|
+
if (row.type === "table" && row.sql !== null) {
|
|
1202
|
+
queries.push(this.columnsQuery(row.name));
|
|
1203
|
+
if (!isVirtualTableSql(row.sql)) {
|
|
1204
|
+
queries.push(this.indexListQuery(row.name), this.foreignKeysQuery(row.name));
|
|
1205
|
+
}
|
|
1206
|
+
} else if (row.type === "index") {
|
|
1207
|
+
queries.push(this.indexColumnsQuery(row.name));
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
for (let start = 0; start < queries.length; start += PREFETCH_BATCH_SIZE) {
|
|
1211
|
+
throwIfAborted2(this.signal);
|
|
1212
|
+
const batch = queries.slice(start, start + PREFETCH_BATCH_SIZE);
|
|
1213
|
+
let results;
|
|
1214
|
+
try {
|
|
1215
|
+
results = await this.connection.queryBatch(batch, this.signal);
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
if (isAbortError(error)) throw error;
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (results.length !== batch.length) {
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
batch.forEach((query, index) => {
|
|
1224
|
+
this.prefetched.set(
|
|
1225
|
+
queryKey(query),
|
|
1226
|
+
results[index].rows
|
|
1227
|
+
);
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
async schemaRows() {
|
|
1232
|
+
if (this.features.schemaQualifiedNames) {
|
|
1233
|
+
let databases;
|
|
1234
|
+
try {
|
|
1235
|
+
databases = await this.rows("PRAGMA database_list");
|
|
1236
|
+
} catch (error) {
|
|
1237
|
+
if (isAbortError(error) || toAsciiLowerCase(this.schemaName) !== "main") throw error;
|
|
1238
|
+
}
|
|
1239
|
+
const known = databases === void 0 || databases.some(
|
|
1240
|
+
(row) => toAsciiLowerCase(toText(row.name) ?? "") === toAsciiLowerCase(this.schemaName)
|
|
1241
|
+
);
|
|
1242
|
+
if (!known) {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1088
1245
|
}
|
|
1089
1246
|
const master = toAsciiLowerCase(this.schemaName) === "temp" ? "sqlite_temp_master" : "sqlite_master";
|
|
1090
1247
|
const rows = await this.rows(
|
|
1091
|
-
`SELECT rowid AS schemaRowid, type, name, tbl_name AS tableName, sql FROM ${this.qualifier}
|
|
1248
|
+
`SELECT rowid AS schemaRowid, type, name, tbl_name AS tableName, sql FROM ${this.qualifier}${master} ORDER BY rowid`
|
|
1092
1249
|
);
|
|
1093
1250
|
return rows.map((row) => ({
|
|
1094
1251
|
schemaRowid: toNumber(row.schemaRowid),
|
|
@@ -1099,13 +1256,14 @@ var CatalogReader = class {
|
|
|
1099
1256
|
}));
|
|
1100
1257
|
}
|
|
1101
1258
|
async tableList() {
|
|
1102
|
-
if (!this.capabilities.supportsTableList) {
|
|
1259
|
+
if (!this.capabilities.supportsTableList || !this.features.extendedPragmas) {
|
|
1103
1260
|
return void 0;
|
|
1104
1261
|
}
|
|
1105
1262
|
try {
|
|
1106
|
-
const rows = await this.rows(
|
|
1107
|
-
|
|
1108
|
-
|
|
1263
|
+
const rows = this.usePragmaFunctions ? await this.rows("SELECT name, type, wr, strict FROM pragma_table_list WHERE schema = ?", [
|
|
1264
|
+
this.schemaName
|
|
1265
|
+
]) : (await this.rows(`PRAGMA ${this.qualifier}table_list`)).filter(
|
|
1266
|
+
(row) => !this.features.schemaQualifiedNames || toAsciiLowerCase(toText(row.schema) ?? "") === toAsciiLowerCase(this.schemaName)
|
|
1109
1267
|
);
|
|
1110
1268
|
return new Map(
|
|
1111
1269
|
rows.map((row) => [
|
|
@@ -1122,13 +1280,27 @@ var CatalogReader = class {
|
|
|
1122
1280
|
return void 0;
|
|
1123
1281
|
}
|
|
1124
1282
|
}
|
|
1283
|
+
/** Runs an extended pragma's query, retrying it in its classic form if the engine refuses it. */
|
|
1284
|
+
async runExtended(flag, query) {
|
|
1285
|
+
try {
|
|
1286
|
+
return await this.run(query());
|
|
1287
|
+
} catch (error) {
|
|
1288
|
+
if (isAbortError(error) || !this[flag]) throw error;
|
|
1289
|
+
this[flag] = false;
|
|
1290
|
+
try {
|
|
1291
|
+
return await this.run(query());
|
|
1292
|
+
} catch {
|
|
1293
|
+
this[flag] = true;
|
|
1294
|
+
throw error;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1125
1298
|
async columns(tableName) {
|
|
1126
|
-
|
|
1127
|
-
const rows = await this.
|
|
1128
|
-
withHidden
|
|
1129
|
-
tableName
|
|
1130
|
-
|
|
1131
|
-
);
|
|
1299
|
+
let withHidden = this.useTableXinfo;
|
|
1300
|
+
const rows = await this.runExtended("useTableXinfo", () => {
|
|
1301
|
+
withHidden = this.useTableXinfo;
|
|
1302
|
+
return this.columnsQuery(tableName);
|
|
1303
|
+
});
|
|
1132
1304
|
return rows.map((row) => {
|
|
1133
1305
|
const hidden = withHidden ? toNumber(row.hidden) : 0;
|
|
1134
1306
|
return {
|
|
@@ -1144,11 +1316,7 @@ var CatalogReader = class {
|
|
|
1144
1316
|
});
|
|
1145
1317
|
}
|
|
1146
1318
|
async indexes(tableName, schemaRows) {
|
|
1147
|
-
const list = await this.
|
|
1148
|
-
"index_list",
|
|
1149
|
-
tableName,
|
|
1150
|
-
'seq, name, "unique" AS isUnique, origin, partial'
|
|
1151
|
-
);
|
|
1319
|
+
const list = await this.run(this.indexListQuery(tableName));
|
|
1152
1320
|
const result = [];
|
|
1153
1321
|
for (const row of list) {
|
|
1154
1322
|
const name = toText(row.name) ?? "";
|
|
@@ -1156,12 +1324,11 @@ var CatalogReader = class {
|
|
|
1156
1324
|
const origin = toText(row.origin) ?? "c";
|
|
1157
1325
|
let columns = [];
|
|
1158
1326
|
try {
|
|
1159
|
-
const keyRows = await this.
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
)
|
|
1164
|
-
columns = keyRows.filter((keyRow) => toNumber(keyRow.isKey ?? keyRow.key) !== 0).map((keyRow) => ({
|
|
1327
|
+
const keyRows = await this.runExtended("useIndexXinfo", () => this.indexColumnsQuery(name));
|
|
1328
|
+
columns = keyRows.filter((keyRow) => {
|
|
1329
|
+
const isKey = keyRow.isKey ?? keyRow.key;
|
|
1330
|
+
return isKey === void 0 || toNumber(isKey) !== 0;
|
|
1331
|
+
}).sort((a, b) => toNumber(a.seqno) - toNumber(b.seqno)).map((keyRow) => ({
|
|
1165
1332
|
name: toText(keyRow.name),
|
|
1166
1333
|
cid: toNumber(keyRow.cid),
|
|
1167
1334
|
descending: toNumber(keyRow.isDesc ?? keyRow.desc) !== 0,
|
|
@@ -1186,11 +1353,7 @@ var CatalogReader = class {
|
|
|
1186
1353
|
return result;
|
|
1187
1354
|
}
|
|
1188
1355
|
async foreignKeys(tableName) {
|
|
1189
|
-
const rows = await this.
|
|
1190
|
-
"foreign_key_list",
|
|
1191
|
-
tableName,
|
|
1192
|
-
'id, seq, "table" AS referencedTable, "from" AS fromColumn, "to" AS toColumn, on_update AS onUpdate, on_delete AS onDelete, "match" AS matchMode'
|
|
1193
|
-
);
|
|
1356
|
+
const rows = await this.run(this.foreignKeysQuery(tableName));
|
|
1194
1357
|
const byId = /* @__PURE__ */ new Map();
|
|
1195
1358
|
const ordered = [...rows].sort(
|
|
1196
1359
|
(a, b) => toNumber(a.id) - toNumber(b.id) || toNumber(a.seq) - toNumber(b.seq)
|
|
@@ -1219,7 +1382,7 @@ var CatalogReader = class {
|
|
|
1219
1382
|
}
|
|
1220
1383
|
async sequences() {
|
|
1221
1384
|
const rows = await this.rows(
|
|
1222
|
-
`SELECT name, CAST(seq AS TEXT) AS value FROM ${this.qualifier}
|
|
1385
|
+
`SELECT name, CAST(seq AS TEXT) AS value FROM ${this.qualifier}sqlite_sequence`
|
|
1223
1386
|
);
|
|
1224
1387
|
return rows.map((row) => ({
|
|
1225
1388
|
tableName: toText(row.name) ?? "",
|
|
@@ -1229,7 +1392,7 @@ var CatalogReader = class {
|
|
|
1229
1392
|
async settings() {
|
|
1230
1393
|
const read = async (pragma) => {
|
|
1231
1394
|
try {
|
|
1232
|
-
const rows = await this.rows(`PRAGMA ${this.qualifier}
|
|
1395
|
+
const rows = await this.rows(`PRAGMA ${this.qualifier}${pragma}`);
|
|
1233
1396
|
const first = rows[0];
|
|
1234
1397
|
return first === void 0 ? void 0 : Object.values(first)[0];
|
|
1235
1398
|
} catch (error) {
|
|
@@ -1778,20 +1941,25 @@ import { isUtf8 } from "buffer";
|
|
|
1778
1941
|
function realLiteralExpression(column) {
|
|
1779
1942
|
return `CASE WHEN ${column} = 9e999 THEN '9.0e+999' WHEN ${column} = -9e999 THEN '-9.0e+999' WHEN ${column} >= -9223372036854775808.0 AND ${column} < 9223372036854775808.0 AND ${column} = CAST(${column} AS INTEGER) THEN CAST(CAST(${column} AS INTEGER) AS TEXT) || '.0' ELSE printf('%!.20g', ${column}) END`;
|
|
1780
1943
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
return
|
|
1944
|
+
var NATIVE_VALUE_TRANSPORT = { hexText: false, hexBlob: false };
|
|
1945
|
+
function valueTransport(utf8Database, binaryTransport) {
|
|
1946
|
+
return binaryTransport === "hex" ? { hexText: utf8Database, hexBlob: true } : NATIVE_VALUE_TRANSPORT;
|
|
1784
1947
|
}
|
|
1785
|
-
function
|
|
1786
|
-
|
|
1787
|
-
|
|
1948
|
+
function columnValueSelect(column, index, utf8Database, transport = NATIVE_VALUE_TRANSPORT) {
|
|
1949
|
+
const textBytes = utf8Database ? `CAST(${column} AS BLOB)` : column;
|
|
1950
|
+
const textBranch = transport.hexText ? `hex(${textBytes})` : textBytes;
|
|
1951
|
+
return `typeof(${column}) AS "t${index}", CASE typeof(${column}) WHEN 'integer' THEN CAST(${column} AS TEXT) WHEN 'real' THEN ${realLiteralExpression(column)} WHEN 'text' THEN ${textBranch} ` + (transport.hexBlob ? `WHEN 'blob' THEN hex(${column}) ` : "") + `ELSE ${column} END AS "v${index}"`;
|
|
1952
|
+
}
|
|
1953
|
+
function asBytes(value, hex) {
|
|
1954
|
+
if (typeof value === "string") {
|
|
1955
|
+
return Buffer.from(value, hex ? "hex" : "utf8");
|
|
1788
1956
|
}
|
|
1789
|
-
if (value instanceof Uint8Array) {
|
|
1790
|
-
return
|
|
1957
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer || Array.isArray(value)) {
|
|
1958
|
+
return toBuffer(value);
|
|
1791
1959
|
}
|
|
1792
1960
|
return Buffer.from(String(value), "utf8");
|
|
1793
1961
|
}
|
|
1794
|
-
function renderColumnLiteral(row, index, textStyle) {
|
|
1962
|
+
function renderColumnLiteral(row, index, textStyle, transport = NATIVE_VALUE_TRANSPORT) {
|
|
1795
1963
|
const storageClass = row[`t${index}`];
|
|
1796
1964
|
const value = row[`v${index}`] ?? null;
|
|
1797
1965
|
switch (storageClass) {
|
|
@@ -1799,17 +1967,17 @@ function renderColumnLiteral(row, index, textStyle) {
|
|
|
1799
1967
|
case "real":
|
|
1800
1968
|
return String(value);
|
|
1801
1969
|
case "text": {
|
|
1802
|
-
if (typeof value === "string") {
|
|
1970
|
+
if (typeof value === "string" && !transport.hexText) {
|
|
1803
1971
|
return renderTextLiteral(value, textStyle);
|
|
1804
1972
|
}
|
|
1805
|
-
const bytes = asBytes(value);
|
|
1973
|
+
const bytes = asBytes(value, transport.hexText);
|
|
1806
1974
|
if (isUtf8(bytes)) {
|
|
1807
1975
|
return renderTextLiteral(bytes.toString("utf8"), textStyle);
|
|
1808
1976
|
}
|
|
1809
1977
|
return Buffer.from(renderTextLiteral(bytes.toString("latin1"), textStyle), "latin1");
|
|
1810
1978
|
}
|
|
1811
1979
|
case "blob":
|
|
1812
|
-
return renderBlobLiteral(asBytes(value));
|
|
1980
|
+
return renderBlobLiteral(asBytes(value, transport.hexBlob));
|
|
1813
1981
|
default:
|
|
1814
1982
|
return "NULL";
|
|
1815
1983
|
}
|
|
@@ -1846,6 +2014,8 @@ async function exportTableDataAsInserts(request) {
|
|
|
1846
2014
|
);
|
|
1847
2015
|
const textStyle = options.rawNewlines ? "raw-newlines" : "escaped";
|
|
1848
2016
|
const utf8Database = (request.encoding ?? "UTF-8").toUpperCase() === "UTF-8";
|
|
2017
|
+
const features = connectionFeatures(connection);
|
|
2018
|
+
const transport = valueTransport(utf8Database, features.binaryTransport);
|
|
1849
2019
|
const warnings = [];
|
|
1850
2020
|
const columns = insertableColumns(table);
|
|
1851
2021
|
const rowidName = options.preserveRowids ? preservableRowidName(table) : null;
|
|
@@ -1877,13 +2047,39 @@ async function exportTableDataAsInserts(request) {
|
|
|
1877
2047
|
const columnList = rowidName ? `(${[rowidName, ...columns.map((column) => quoteIdentifierIfNeeded(column.name))].join(",")})` : "";
|
|
1878
2048
|
const statementPrefix = `INSERT INTO ${quoteIdentifierIfNeeded(table.name)}${columnList} VALUES`;
|
|
1879
2049
|
const selectList = [
|
|
1880
|
-
...rowidName ? [columnValueSelect(rowidName, 0, utf8Database)] : [],
|
|
2050
|
+
...rowidName ? [columnValueSelect(rowidName, 0, utf8Database, transport)] : [],
|
|
1881
2051
|
...columns.map(
|
|
1882
|
-
(column, index) => columnValueSelect(
|
|
2052
|
+
(column, index) => columnValueSelect(
|
|
2053
|
+
quoteIdentifier(column.name),
|
|
2054
|
+
index + (rowidName ? 1 : 0),
|
|
2055
|
+
utf8Database,
|
|
2056
|
+
transport
|
|
2057
|
+
)
|
|
1883
2058
|
)
|
|
1884
2059
|
].join(", ");
|
|
1885
2060
|
const valueCount = columns.length + (rowidName ? 1 : 0);
|
|
1886
|
-
const
|
|
2061
|
+
const from = features.schemaQualifiedNames ? quoteQualifiedIdentifier(schemaName, table.name) : quoteIdentifier(table.name);
|
|
2062
|
+
const filter = systemRowFilterCondition(
|
|
2063
|
+
table,
|
|
2064
|
+
request.systemRowFilter,
|
|
2065
|
+
features.reservedNamePrefixes
|
|
2066
|
+
);
|
|
2067
|
+
const rows = features.pagedReadSize === void 0 ? connection.stream(
|
|
2068
|
+
{ sql: `SELECT ${selectList} FROM ${from}${filter === null ? "" : ` WHERE ${filter}`}` },
|
|
2069
|
+
{
|
|
2070
|
+
...signal === void 0 ? {} : { signal },
|
|
2071
|
+
...options.streamBatchSize === void 0 ? {} : { batchSize: options.streamBatchSize }
|
|
2072
|
+
}
|
|
2073
|
+
) : readPages({
|
|
2074
|
+
connection,
|
|
2075
|
+
selectList,
|
|
2076
|
+
from,
|
|
2077
|
+
filter,
|
|
2078
|
+
pageSize: features.pagedReadSize,
|
|
2079
|
+
key: pageKey(table, columns, rowidName ? 1 : 0, request.primaryKeyIndex),
|
|
2080
|
+
transport,
|
|
2081
|
+
signal
|
|
2082
|
+
});
|
|
1887
2083
|
let rowsExported = 0;
|
|
1888
2084
|
let statementsWritten = 0;
|
|
1889
2085
|
let output = new SqlChunkBuilder();
|
|
@@ -1924,13 +2120,7 @@ async function exportTableDataAsInserts(request) {
|
|
|
1924
2120
|
};
|
|
1925
2121
|
progress("started");
|
|
1926
2122
|
try {
|
|
1927
|
-
for await (const row of
|
|
1928
|
-
{ sql },
|
|
1929
|
-
{
|
|
1930
|
-
...signal === void 0 ? {} : { signal },
|
|
1931
|
-
...options.streamBatchSize === void 0 ? {} : { batchSize: options.streamBatchSize }
|
|
1932
|
-
}
|
|
1933
|
-
)) {
|
|
2123
|
+
for await (const row of rows) {
|
|
1934
2124
|
throwIfAborted2(signal);
|
|
1935
2125
|
const tuple = new SqlChunkBuilder();
|
|
1936
2126
|
tuple.append("(");
|
|
@@ -1938,7 +2128,7 @@ async function exportTableDataAsInserts(request) {
|
|
|
1938
2128
|
if (index > 0) {
|
|
1939
2129
|
tuple.append(",");
|
|
1940
2130
|
}
|
|
1941
|
-
tuple.append(renderColumnLiteral(row, index, textStyle));
|
|
2131
|
+
tuple.append(renderColumnLiteral(row, index, textStyle, transport));
|
|
1942
2132
|
}
|
|
1943
2133
|
tuple.append(")");
|
|
1944
2134
|
if (statement && (statementRows >= maxRowsPerStatement || statement.length + tuple.length + 2 > maxStatementBytes)) {
|
|
@@ -1982,16 +2172,121 @@ async function exportTableDataAsInserts(request) {
|
|
|
1982
2172
|
throw error;
|
|
1983
2173
|
}
|
|
1984
2174
|
}
|
|
1985
|
-
function
|
|
1986
|
-
if (filter === void 0
|
|
1987
|
-
return
|
|
2175
|
+
function systemRowFilterCondition(table, filter, reservedPrefixes) {
|
|
2176
|
+
if (table.kind !== "system" || filter === void 0 && reservedPrefixes.length === 0) {
|
|
2177
|
+
return null;
|
|
1988
2178
|
}
|
|
1989
2179
|
const column = table.name === "sqlite_sequence" ? "name" : "tbl";
|
|
1990
|
-
const
|
|
1991
|
-
if (
|
|
1992
|
-
|
|
2180
|
+
const conditions = [];
|
|
2181
|
+
if (filter !== void 0) {
|
|
2182
|
+
const names = [...new Set(filter.map(toAsciiLowerCase))].map(quoteStringLiteral);
|
|
2183
|
+
if (names.length === 0) {
|
|
2184
|
+
return "0";
|
|
2185
|
+
}
|
|
2186
|
+
conditions.push(`lower(${column}) IN (${names.join(",")})`);
|
|
2187
|
+
}
|
|
2188
|
+
for (const prefix of reservedPrefixes) {
|
|
2189
|
+
const lower = toAsciiLowerCase(prefix);
|
|
2190
|
+
conditions.push(
|
|
2191
|
+
`coalesce(substr(lower(${column}), 1, ${lower.length}) <> ${quoteStringLiteral(lower)}, 1)`
|
|
2192
|
+
);
|
|
1993
2193
|
}
|
|
1994
|
-
return
|
|
2194
|
+
return conditions.join(" AND ");
|
|
2195
|
+
}
|
|
2196
|
+
function pageKey(table, columns, valueOffset, primaryKeyIndex) {
|
|
2197
|
+
if (table.kind === "virtual") {
|
|
2198
|
+
return { kind: "offset" };
|
|
2199
|
+
}
|
|
2200
|
+
if (!table.withoutRowid) {
|
|
2201
|
+
if (table.rowidAliasColumn !== null) {
|
|
2202
|
+
return { kind: "rowid", expression: quoteIdentifier(table.rowidAliasColumn) };
|
|
2203
|
+
}
|
|
2204
|
+
const names = new Set(table.columns.map((column) => toAsciiLowerCase(column.name)));
|
|
2205
|
+
const name = ROWID_NAMES.find((candidate) => !names.has(candidate));
|
|
2206
|
+
return name === void 0 ? { kind: "offset" } : { kind: "rowid", expression: name };
|
|
2207
|
+
}
|
|
2208
|
+
const valueIndexes = new Map(
|
|
2209
|
+
columns.map((column, index) => [toAsciiLowerCase(column.name), index + valueOffset])
|
|
2210
|
+
);
|
|
2211
|
+
const keyColumns = primaryKeyIndex && primaryKeyIndex.columns.length > 0 ? primaryKeyIndex.columns.map((column) => ({
|
|
2212
|
+
name: column.name,
|
|
2213
|
+
descending: column.descending,
|
|
2214
|
+
collation: column.collation
|
|
2215
|
+
})) : [...table.columns].filter((column) => column.primaryKeyPosition > 0).sort((a, b) => a.primaryKeyPosition - b.primaryKeyPosition).map((column) => ({ name: column.name, descending: false, collation: null }));
|
|
2216
|
+
const result = [];
|
|
2217
|
+
for (const column of keyColumns) {
|
|
2218
|
+
const valueIndex = column.name === null ? void 0 : valueIndexes.get(toAsciiLowerCase(column.name));
|
|
2219
|
+
if (column.name === null || valueIndex === void 0) {
|
|
2220
|
+
return { kind: "offset" };
|
|
2221
|
+
}
|
|
2222
|
+
const collation = column.collation === null || toAsciiLowerCase(column.collation) === "binary" ? "" : ` COLLATE ${quoteIdentifier(column.collation)}`;
|
|
2223
|
+
result.push({
|
|
2224
|
+
expression: `${quoteIdentifier(column.name)}${collation}`,
|
|
2225
|
+
valueIndex,
|
|
2226
|
+
descending: column.descending
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
return result.length === 0 ? { kind: "offset" } : { kind: "primaryKey", columns: result };
|
|
2230
|
+
}
|
|
2231
|
+
var PAGE_KEY_ALIAS = "dbgate_page_key";
|
|
2232
|
+
async function* readPages(request) {
|
|
2233
|
+
const { connection, selectList, from, filter, pageSize, key, transport, signal } = request;
|
|
2234
|
+
let after = null;
|
|
2235
|
+
let offset = 0;
|
|
2236
|
+
for (; ; ) {
|
|
2237
|
+
throwIfAborted2(signal);
|
|
2238
|
+
const conditions = [filter, after].filter((condition) => condition !== null);
|
|
2239
|
+
const where = conditions.length === 0 ? "" : ` WHERE ${conditions.map((c) => `(${c})`).join(" AND ")}`;
|
|
2240
|
+
let sql;
|
|
2241
|
+
if (key.kind === "rowid") {
|
|
2242
|
+
sql = `SELECT ${selectList}, CAST(${key.expression} AS TEXT) AS "${PAGE_KEY_ALIAS}" FROM ${from}${where} ORDER BY ${key.expression} LIMIT ${pageSize}`;
|
|
2243
|
+
} else if (key.kind === "primaryKey") {
|
|
2244
|
+
const order = key.columns.map((column) => `${column.expression}${column.descending ? " DESC" : ""}`).join(", ");
|
|
2245
|
+
sql = `SELECT ${selectList} FROM ${from}${where} ORDER BY ${order} LIMIT ${pageSize}`;
|
|
2246
|
+
} else {
|
|
2247
|
+
sql = `SELECT ${selectList} FROM ${from}${where} LIMIT ${pageSize} OFFSET ${offset}`;
|
|
2248
|
+
}
|
|
2249
|
+
const { rows } = await connection.query({ sql }, signal);
|
|
2250
|
+
for (const row of rows) {
|
|
2251
|
+
yield row;
|
|
2252
|
+
}
|
|
2253
|
+
if (rows.length < pageSize) {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const last = rows[rows.length - 1];
|
|
2257
|
+
if (key.kind === "rowid") {
|
|
2258
|
+
after = `${key.expression} > ${String(last[PAGE_KEY_ALIAS])}`;
|
|
2259
|
+
} else if (key.kind === "primaryKey") {
|
|
2260
|
+
after = keyAfterCondition(key.columns, last, transport);
|
|
2261
|
+
} else {
|
|
2262
|
+
offset += rows.length;
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
function keyAfterCondition(columns, row, transport) {
|
|
2267
|
+
const literals = columns.map((column) => keyLiteral(row, column.valueIndex, transport));
|
|
2268
|
+
const alternatives = [];
|
|
2269
|
+
for (let position = 0; position < columns.length; position++) {
|
|
2270
|
+
const terms = [];
|
|
2271
|
+
for (let previous = 0; previous < position; previous++) {
|
|
2272
|
+
terms.push(
|
|
2273
|
+
`${columns[previous].expression} = ${literals[previous]}`
|
|
2274
|
+
);
|
|
2275
|
+
}
|
|
2276
|
+
const column = columns[position];
|
|
2277
|
+
terms.push(`${column.expression} ${column.descending ? "<" : ">"} ${literals[position]}`);
|
|
2278
|
+
alternatives.push(terms.length === 1 ? terms[0] : `(${terms.join(" AND ")})`);
|
|
2279
|
+
}
|
|
2280
|
+
return alternatives.join(" OR ");
|
|
2281
|
+
}
|
|
2282
|
+
function keyLiteral(row, index, transport) {
|
|
2283
|
+
const literal = renderColumnLiteral(row, index, "escaped", transport);
|
|
2284
|
+
if (typeof literal === "string") {
|
|
2285
|
+
return literal;
|
|
2286
|
+
}
|
|
2287
|
+
const value = row[`v${index}`] ?? null;
|
|
2288
|
+
const bytes = typeof value === "string" ? Buffer.from(value, transport.hexText ? "hex" : "utf8") : toBuffer(value);
|
|
2289
|
+
return `CAST(X'${bytes.toString("hex").toUpperCase()}' AS TEXT)`;
|
|
1995
2290
|
}
|
|
1996
2291
|
|
|
1997
2292
|
// src/compatibility/check.ts
|
|
@@ -3600,6 +3895,9 @@ async function dumpSqlite(connectionInput, options, output, onProgress, signal)
|
|
|
3600
3895
|
if (!table) {
|
|
3601
3896
|
return false;
|
|
3602
3897
|
}
|
|
3898
|
+
const primaryKeyIndex = table.withoutRowid ? introspection.database.indexes.find(
|
|
3899
|
+
(index) => index.origin === "pk" && foldSqliteName(index.tableName) === foldSqliteName(table.name)
|
|
3900
|
+
) : void 0;
|
|
3603
3901
|
const result = await exportTableDataAsInserts({
|
|
3604
3902
|
connection: acquired.connection,
|
|
3605
3903
|
schemaName: introspection.database.schemaName,
|
|
@@ -3607,6 +3905,7 @@ async function dumpSqlite(connectionInput, options, output, onProgress, signal)
|
|
|
3607
3905
|
writer,
|
|
3608
3906
|
encoding: introspection.database.encoding,
|
|
3609
3907
|
...entry.systemRowFilter === void 0 ? {} : { systemRowFilter: entry.systemRowFilter },
|
|
3908
|
+
...primaryKeyIndex === void 0 ? {} : { primaryKeyIndex },
|
|
3610
3909
|
...options.dataExport === void 0 ? {} : { options: options.dataExport },
|
|
3611
3910
|
...signal === void 0 ? {} : { signal },
|
|
3612
3911
|
...onProgress === void 0 ? {} : { onProgress }
|
|
@@ -3631,13 +3930,21 @@ async function dumpSqlite(connectionInput, options, output, onProgress, signal)
|
|
|
3631
3930
|
onTableData
|
|
3632
3931
|
});
|
|
3633
3932
|
onProgress?.({ phase: "finalizing", bytesWritten: renderResult.bytesWritten });
|
|
3634
|
-
const sessionWarnings =
|
|
3635
|
-
|
|
3933
|
+
const sessionWarnings = [];
|
|
3934
|
+
if (session.joinedExistingTransaction) {
|
|
3935
|
+
sessionWarnings.push({
|
|
3636
3936
|
severity: "info",
|
|
3637
3937
|
code: "dump-joined-caller-transaction",
|
|
3638
3938
|
message: "The connection was already inside a transaction, so the dump was read within it and includes that transaction's uncommitted changes."
|
|
3639
|
-
}
|
|
3640
|
-
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
if (session.snapshotUnavailable) {
|
|
3942
|
+
sessionWarnings.push({
|
|
3943
|
+
severity: "warning",
|
|
3944
|
+
code: "snapshot-unavailable",
|
|
3945
|
+
message: "The database does not support transactions, so the dump was not read from one snapshot: changes committed while it ran may be partly included."
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3641
3948
|
return {
|
|
3642
3949
|
bytesWritten: renderResult.bytesWritten,
|
|
3643
3950
|
renderedDumpIds: renderResult.renderedDumpIds,
|
|
@@ -3676,6 +3983,7 @@ export {
|
|
|
3676
3983
|
DEFENSIVE_WARNING_COMMENT,
|
|
3677
3984
|
InvalidTextEncodingError,
|
|
3678
3985
|
MalformedSqlDumpError,
|
|
3986
|
+
NATIVE_VALUE_TRANSPORT,
|
|
3679
3987
|
OperationCancelledError,
|
|
3680
3988
|
RestoreError,
|
|
3681
3989
|
RestoreExecutionError,
|
|
@@ -3701,6 +4009,7 @@ export {
|
|
|
3701
4009
|
classifyWord,
|
|
3702
4010
|
columnValueSelect,
|
|
3703
4011
|
completeTransition,
|
|
4012
|
+
connectionFeatures,
|
|
3704
4013
|
createArchiveIdentity,
|
|
3705
4014
|
createCanonicalIdentity,
|
|
3706
4015
|
createDumpId,
|
|
@@ -3767,9 +4076,11 @@ export {
|
|
|
3767
4076
|
throwIfAborted2 as throwIfAborted,
|
|
3768
4077
|
toAsciiLowerCase,
|
|
3769
4078
|
toAsciiUpperCase,
|
|
4079
|
+
toBuffer,
|
|
3770
4080
|
toByteStringChunks,
|
|
3771
4081
|
toNumber,
|
|
3772
4082
|
toText,
|
|
3773
|
-
unsupportedFeatureDiagnostics
|
|
4083
|
+
unsupportedFeatureDiagnostics,
|
|
4084
|
+
valueTransport
|
|
3774
4085
|
};
|
|
3775
4086
|
//# sourceMappingURL=index.js.map
|