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/dist/index.cjs CHANGED
@@ -26,6 +26,7 @@ __export(src_exports, {
26
26
  DEFENSIVE_WARNING_COMMENT: () => DEFENSIVE_WARNING_COMMENT,
27
27
  InvalidTextEncodingError: () => InvalidTextEncodingError,
28
28
  MalformedSqlDumpError: () => MalformedSqlDumpError,
29
+ NATIVE_VALUE_TRANSPORT: () => NATIVE_VALUE_TRANSPORT,
29
30
  OperationCancelledError: () => OperationCancelledError,
30
31
  RestoreError: () => RestoreError,
31
32
  RestoreExecutionError: () => RestoreExecutionError,
@@ -51,6 +52,7 @@ __export(src_exports, {
51
52
  classifyWord: () => classifyWord,
52
53
  columnValueSelect: () => columnValueSelect,
53
54
  completeTransition: () => completeTransition,
55
+ connectionFeatures: () => connectionFeatures,
54
56
  createArchiveIdentity: () => createArchiveIdentity,
55
57
  createCanonicalIdentity: () => createCanonicalIdentity,
56
58
  createDumpId: () => createDumpId,
@@ -117,10 +119,12 @@ __export(src_exports, {
117
119
  throwIfAborted: () => throwIfAborted2,
118
120
  toAsciiLowerCase: () => toAsciiLowerCase,
119
121
  toAsciiUpperCase: () => toAsciiUpperCase,
122
+ toBuffer: () => toBuffer,
120
123
  toByteStringChunks: () => toByteStringChunks,
121
124
  toNumber: () => toNumber,
122
125
  toText: () => toText,
123
- unsupportedFeatureDiagnostics: () => unsupportedFeatureDiagnostics
126
+ unsupportedFeatureDiagnostics: () => unsupportedFeatureDiagnostics,
127
+ valueTransport: () => valueTransport
124
128
  });
125
129
  module.exports = __toCommonJS(src_exports);
126
130
 
@@ -167,19 +171,46 @@ function toText(value) {
167
171
  if (typeof value === "string") {
168
172
  return value;
169
173
  }
170
- if (value instanceof Uint8Array) {
171
- return Buffer.from(value).toString("utf8");
174
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer || Array.isArray(value)) {
175
+ return toBuffer(value).toString("utf8");
172
176
  }
173
177
  return String(value);
174
178
  }
179
+ function toBuffer(value) {
180
+ if (Buffer.isBuffer(value)) {
181
+ return value;
182
+ }
183
+ if (value instanceof Uint8Array) {
184
+ return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
185
+ }
186
+ if (value instanceof ArrayBuffer) {
187
+ return Buffer.from(value);
188
+ }
189
+ return Buffer.from(value);
190
+ }
191
+ function connectionFeatures(connection) {
192
+ const features = connection.features ?? {};
193
+ const pageSize = features.pagedReadSize;
194
+ return {
195
+ transactions: features.transactions ?? true,
196
+ pragmaFunctions: features.pragmaFunctions ?? true,
197
+ extendedPragmas: features.extendedPragmas ?? true,
198
+ schemaQualifiedNames: features.schemaQualifiedNames ?? true,
199
+ binaryTransport: features.binaryTransport ?? "native",
200
+ pagedReadSize: pageSize === void 0 || !Number.isFinite(pageSize) ? void 0 : Math.max(1, Math.floor(pageSize)),
201
+ reservedNamePrefixes: features.reservedNamePrefixes ?? []
202
+ };
203
+ }
175
204
 
176
205
  // src/connection/session.ts
177
206
  var DUMP_SAVEPOINT = "dbgate_sqlite_dump";
178
207
  async function beginSqliteDumpSession(connection, options, signal) {
179
- const consistency = options?.consistency ?? "snapshot";
208
+ const requested = options?.consistency ?? "snapshot";
180
209
  const joinedExistingTransaction = connection.isInTransaction?.() ?? false;
210
+ const snapshotUnavailable = requested === "snapshot" && !connectionFeatures(connection).transactions;
211
+ const consistency = snapshotUnavailable ? "none" : requested;
181
212
  if (consistency === "none") {
182
- return { consistency, joinedExistingTransaction, finish: async () => {
213
+ return { consistency, joinedExistingTransaction, snapshotUnavailable, finish: async () => {
183
214
  } };
184
215
  }
185
216
  await executeStatement(connection, `SAVEPOINT ${DUMP_SAVEPOINT}`, signal);
@@ -198,7 +229,7 @@ async function beginSqliteDumpSession(connection, options, signal) {
198
229
  await finish();
199
230
  throw error;
200
231
  }
201
- return { consistency, joinedExistingTransaction, finish };
232
+ return { consistency, joinedExistingTransaction, snapshotUnavailable: false, finish };
202
233
  }
203
234
 
204
235
  // src/version/types.ts
@@ -1024,17 +1055,34 @@ async function introspectSqlite(connection, options, signal) {
1024
1055
  const schemaName = options?.schemaName ?? "main";
1025
1056
  const version = await detectSqliteVersion(connection, signal);
1026
1057
  const capabilities = detectSqliteCapabilities(version);
1058
+ const features = connectionFeatures(connection);
1027
1059
  const diagnostics = [];
1028
- const catalog = new CatalogReader(connection, schemaName, capabilities, signal);
1029
- const schemaRows = await catalog.schemaRows();
1030
- if (schemaRows === null) {
1060
+ if (!features.schemaQualifiedNames && toAsciiLowerCase(schemaName) !== "main") {
1061
+ throw new SqliteDumperError(
1062
+ "schema-not-found",
1063
+ `This connection can only read the main database, not ${JSON.stringify(schemaName)}`
1064
+ );
1065
+ }
1066
+ const catalog = new CatalogReader(connection, schemaName, capabilities, features, signal);
1067
+ const allSchemaRows = await catalog.schemaRows();
1068
+ if (allSchemaRows === null) {
1031
1069
  throw new SqliteDumperError(
1032
1070
  "schema-not-found",
1033
1071
  `No database named ${JSON.stringify(schemaName)} is attached to this connection`
1034
1072
  );
1035
1073
  }
1074
+ const reserved = allSchemaRows.filter((row) => isReservedRow(row, features.reservedNamePrefixes));
1075
+ const schemaRows = allSchemaRows.filter((row) => !reserved.includes(row));
1076
+ if (reserved.length > 0) {
1077
+ diagnostics.push({
1078
+ severity: "info",
1079
+ code: "reserved-objects-skipped",
1080
+ 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(", ")}.`
1081
+ });
1082
+ }
1036
1083
  const tableList = await catalog.tableList();
1037
1084
  const virtualTableNames = schemaRows.filter((row) => row.type === "table" && isVirtualTableSql(row.sql)).map((row) => row.name);
1085
+ await catalog.prefetch(schemaRows);
1038
1086
  const tables = [];
1039
1087
  const indexes = [];
1040
1088
  const foreignKeys = [];
@@ -1135,6 +1183,20 @@ async function introspectSqlite(connection, options, signal) {
1135
1183
  function errorMessage(error) {
1136
1184
  return error instanceof Error ? error.message : String(error);
1137
1185
  }
1186
+ function isReservedRow(row, prefixes) {
1187
+ if (prefixes.length === 0) {
1188
+ return false;
1189
+ }
1190
+ const names = [toAsciiLowerCase(row.name), toAsciiLowerCase(row.tableName)];
1191
+ return prefixes.some((prefix) => {
1192
+ const lower = toAsciiLowerCase(prefix);
1193
+ return names.some((name) => name.startsWith(lower));
1194
+ });
1195
+ }
1196
+ var PREFETCH_BATCH_SIZE = 50;
1197
+ function queryKey(query) {
1198
+ return `${query.sql}\0${JSON.stringify(query.parameters ?? [])}`;
1199
+ }
1138
1200
  function isVirtualTableSql(sql) {
1139
1201
  return sql !== null && sql.startsWith("CREATE VIRTUAL TABLE");
1140
1202
  }
@@ -1178,43 +1240,142 @@ function findRowidAlias(columns, indexes) {
1178
1240
  return column.name;
1179
1241
  }
1180
1242
  var CatalogReader = class {
1181
- constructor(connection, schemaName, capabilities, signal) {
1243
+ constructor(connection, schemaName, capabilities, features, signal) {
1182
1244
  this.connection = connection;
1183
1245
  this.schemaName = schemaName;
1184
1246
  this.capabilities = capabilities;
1247
+ this.features = features;
1185
1248
  this.signal = signal;
1186
- this.qualifier = quoteIdentifier(schemaName);
1249
+ this.qualifier = features.schemaQualifiedNames ? `${quoteIdentifier(schemaName)}.` : "";
1250
+ this.usePragmaFunctions = capabilities.supportsPragmaFunctions && features.pragmaFunctions;
1251
+ this.useTableXinfo = capabilities.supportsTableXinfo && features.extendedPragmas;
1252
+ this.useIndexXinfo = features.extendedPragmas;
1187
1253
  }
1188
1254
  connection;
1189
1255
  schemaName;
1190
1256
  capabilities;
1257
+ features;
1191
1258
  signal;
1259
+ /** `"schema".`, or empty for a connection that cannot qualify names. */
1192
1260
  qualifier;
1261
+ usePragmaFunctions;
1262
+ // Not readonly: an engine that refuses the extended pragmas outright (some
1263
+ // remote engines allow only the classic ones) is switched to the classic
1264
+ // ones the first time one of them succeeds where its extended form failed.
1265
+ useTableXinfo;
1266
+ useIndexXinfo;
1267
+ prefetched = /* @__PURE__ */ new Map();
1193
1268
  async rows(sql, parameters) {
1194
- const result = await this.connection.query(
1195
- parameters === void 0 ? { sql } : { sql, parameters },
1196
- this.signal
1197
- );
1269
+ return this.run(parameters === void 0 ? { sql } : { sql, parameters });
1270
+ }
1271
+ async run(query) {
1272
+ const prefetched = this.prefetched.get(queryKey(query));
1273
+ if (prefetched !== void 0) {
1274
+ return prefetched;
1275
+ }
1276
+ const result = await this.connection.query(query, this.signal);
1198
1277
  return result.rows;
1199
1278
  }
1200
- /** Runs `PRAGMA schema.name(argument)`, as a table-valued function when the library has them. */
1201
- async pragma(name, argument, columns) {
1202
- if (this.capabilities.supportsPragmaFunctions) {
1203
- return this.rows(`SELECT ${columns} FROM pragma_${name}(?, ?)`, [argument, this.schemaName]);
1279
+ /** `PRAGMA schema.name(argument)`, as a table-valued function when the library has them. */
1280
+ pragmaQuery(name, argument, columns) {
1281
+ if (this.usePragmaFunctions) {
1282
+ return {
1283
+ sql: `SELECT ${columns} FROM pragma_${name}(?, ?)`,
1284
+ parameters: [argument, this.schemaName]
1285
+ };
1204
1286
  }
1205
- return this.rows(`PRAGMA ${this.qualifier}.${name}(${quoteStringLiteral(argument)})`);
1287
+ return { sql: `PRAGMA ${this.qualifier}${name}(${quoteStringLiteral(argument)})` };
1206
1288
  }
1207
- async schemaRows() {
1208
- const databases = await this.rows("PRAGMA database_list");
1209
- const known = databases.some(
1210
- (row) => toAsciiLowerCase(toText(row.name) ?? "") === toAsciiLowerCase(this.schemaName)
1289
+ columnsQuery(tableName) {
1290
+ const withHidden = this.useTableXinfo;
1291
+ return this.pragmaQuery(
1292
+ withHidden ? "table_xinfo" : "table_info",
1293
+ tableName,
1294
+ `cid, name, type, "notnull" AS isNotNull, dflt_value AS defaultValue, pk${withHidden ? ", hidden" : ""}`
1211
1295
  );
1212
- if (!known) {
1213
- return null;
1296
+ }
1297
+ indexListQuery(tableName) {
1298
+ return this.pragmaQuery(
1299
+ "index_list",
1300
+ tableName,
1301
+ 'seq, name, "unique" AS isUnique, origin, partial'
1302
+ );
1303
+ }
1304
+ indexColumnsQuery(indexName) {
1305
+ return this.useIndexXinfo ? this.pragmaQuery(
1306
+ "index_xinfo",
1307
+ indexName,
1308
+ 'seqno, cid, name, "desc" AS isDesc, coll, "key" AS isKey'
1309
+ ) : this.pragmaQuery("index_info", indexName, "seqno, cid, name");
1310
+ }
1311
+ foreignKeysQuery(tableName) {
1312
+ return this.pragmaQuery(
1313
+ "foreign_key_list",
1314
+ tableName,
1315
+ 'id, seq, "table" AS referencedTable, "from" AS fromColumn, "to" AS toColumn, on_update AS onUpdate, on_delete AS onDelete, "match" AS matchMode'
1316
+ );
1317
+ }
1318
+ /**
1319
+ * Fetches the per-table catalog of every object up front through
1320
+ * {@link SqliteConnection.queryBatch}, when the connection has it — for a
1321
+ * remote engine, a few requests instead of several per table. A batch that
1322
+ * fails is simply not cached: its queries then run one by one later, where
1323
+ * a failing one is handled (or reported) as it would be without batching.
1324
+ */
1325
+ async prefetch(schemaRows) {
1326
+ if (!this.connection.queryBatch) {
1327
+ return;
1328
+ }
1329
+ const queries = [];
1330
+ for (const row of schemaRows) {
1331
+ if (row.type === "table" && row.sql !== null) {
1332
+ queries.push(this.columnsQuery(row.name));
1333
+ if (!isVirtualTableSql(row.sql)) {
1334
+ queries.push(this.indexListQuery(row.name), this.foreignKeysQuery(row.name));
1335
+ }
1336
+ } else if (row.type === "index") {
1337
+ queries.push(this.indexColumnsQuery(row.name));
1338
+ }
1339
+ }
1340
+ for (let start = 0; start < queries.length; start += PREFETCH_BATCH_SIZE) {
1341
+ throwIfAborted2(this.signal);
1342
+ const batch = queries.slice(start, start + PREFETCH_BATCH_SIZE);
1343
+ let results;
1344
+ try {
1345
+ results = await this.connection.queryBatch(batch, this.signal);
1346
+ } catch (error) {
1347
+ if (isAbortError(error)) throw error;
1348
+ continue;
1349
+ }
1350
+ if (results.length !== batch.length) {
1351
+ continue;
1352
+ }
1353
+ batch.forEach((query, index) => {
1354
+ this.prefetched.set(
1355
+ queryKey(query),
1356
+ results[index].rows
1357
+ );
1358
+ });
1359
+ }
1360
+ }
1361
+ async schemaRows() {
1362
+ if (this.features.schemaQualifiedNames) {
1363
+ let databases;
1364
+ try {
1365
+ databases = await this.rows("PRAGMA database_list");
1366
+ } catch (error) {
1367
+ if (isAbortError(error) || toAsciiLowerCase(this.schemaName) !== "main") throw error;
1368
+ }
1369
+ const known = databases === void 0 || databases.some(
1370
+ (row) => toAsciiLowerCase(toText(row.name) ?? "") === toAsciiLowerCase(this.schemaName)
1371
+ );
1372
+ if (!known) {
1373
+ return null;
1374
+ }
1214
1375
  }
1215
1376
  const master = toAsciiLowerCase(this.schemaName) === "temp" ? "sqlite_temp_master" : "sqlite_master";
1216
1377
  const rows = await this.rows(
1217
- `SELECT rowid AS schemaRowid, type, name, tbl_name AS tableName, sql FROM ${this.qualifier}.${master} ORDER BY rowid`
1378
+ `SELECT rowid AS schemaRowid, type, name, tbl_name AS tableName, sql FROM ${this.qualifier}${master} ORDER BY rowid`
1218
1379
  );
1219
1380
  return rows.map((row) => ({
1220
1381
  schemaRowid: toNumber(row.schemaRowid),
@@ -1225,13 +1386,14 @@ var CatalogReader = class {
1225
1386
  }));
1226
1387
  }
1227
1388
  async tableList() {
1228
- if (!this.capabilities.supportsTableList) {
1389
+ if (!this.capabilities.supportsTableList || !this.features.extendedPragmas) {
1229
1390
  return void 0;
1230
1391
  }
1231
1392
  try {
1232
- const rows = await this.rows(
1233
- "SELECT name, type, wr, strict FROM pragma_table_list WHERE schema = ?",
1234
- [this.schemaName]
1393
+ const rows = this.usePragmaFunctions ? await this.rows("SELECT name, type, wr, strict FROM pragma_table_list WHERE schema = ?", [
1394
+ this.schemaName
1395
+ ]) : (await this.rows(`PRAGMA ${this.qualifier}table_list`)).filter(
1396
+ (row) => !this.features.schemaQualifiedNames || toAsciiLowerCase(toText(row.schema) ?? "") === toAsciiLowerCase(this.schemaName)
1235
1397
  );
1236
1398
  return new Map(
1237
1399
  rows.map((row) => [
@@ -1248,13 +1410,27 @@ var CatalogReader = class {
1248
1410
  return void 0;
1249
1411
  }
1250
1412
  }
1413
+ /** Runs an extended pragma's query, retrying it in its classic form if the engine refuses it. */
1414
+ async runExtended(flag, query) {
1415
+ try {
1416
+ return await this.run(query());
1417
+ } catch (error) {
1418
+ if (isAbortError(error) || !this[flag]) throw error;
1419
+ this[flag] = false;
1420
+ try {
1421
+ return await this.run(query());
1422
+ } catch {
1423
+ this[flag] = true;
1424
+ throw error;
1425
+ }
1426
+ }
1427
+ }
1251
1428
  async columns(tableName) {
1252
- const withHidden = this.capabilities.supportsTableXinfo;
1253
- const rows = await this.pragma(
1254
- withHidden ? "table_xinfo" : "table_info",
1255
- tableName,
1256
- `cid, name, type, "notnull" AS isNotNull, dflt_value AS defaultValue, pk${withHidden ? ", hidden" : ""}`
1257
- );
1429
+ let withHidden = this.useTableXinfo;
1430
+ const rows = await this.runExtended("useTableXinfo", () => {
1431
+ withHidden = this.useTableXinfo;
1432
+ return this.columnsQuery(tableName);
1433
+ });
1258
1434
  return rows.map((row) => {
1259
1435
  const hidden = withHidden ? toNumber(row.hidden) : 0;
1260
1436
  return {
@@ -1270,11 +1446,7 @@ var CatalogReader = class {
1270
1446
  });
1271
1447
  }
1272
1448
  async indexes(tableName, schemaRows) {
1273
- const list = await this.pragma(
1274
- "index_list",
1275
- tableName,
1276
- 'seq, name, "unique" AS isUnique, origin, partial'
1277
- );
1449
+ const list = await this.run(this.indexListQuery(tableName));
1278
1450
  const result = [];
1279
1451
  for (const row of list) {
1280
1452
  const name = toText(row.name) ?? "";
@@ -1282,12 +1454,11 @@ var CatalogReader = class {
1282
1454
  const origin = toText(row.origin) ?? "c";
1283
1455
  let columns = [];
1284
1456
  try {
1285
- const keyRows = await this.pragma(
1286
- "index_xinfo",
1287
- name,
1288
- 'seqno, cid, name, "desc" AS isDesc, coll, "key" AS isKey'
1289
- );
1290
- columns = keyRows.filter((keyRow) => toNumber(keyRow.isKey ?? keyRow.key) !== 0).map((keyRow) => ({
1457
+ const keyRows = await this.runExtended("useIndexXinfo", () => this.indexColumnsQuery(name));
1458
+ columns = keyRows.filter((keyRow) => {
1459
+ const isKey = keyRow.isKey ?? keyRow.key;
1460
+ return isKey === void 0 || toNumber(isKey) !== 0;
1461
+ }).sort((a, b) => toNumber(a.seqno) - toNumber(b.seqno)).map((keyRow) => ({
1291
1462
  name: toText(keyRow.name),
1292
1463
  cid: toNumber(keyRow.cid),
1293
1464
  descending: toNumber(keyRow.isDesc ?? keyRow.desc) !== 0,
@@ -1312,11 +1483,7 @@ var CatalogReader = class {
1312
1483
  return result;
1313
1484
  }
1314
1485
  async foreignKeys(tableName) {
1315
- const rows = await this.pragma(
1316
- "foreign_key_list",
1317
- tableName,
1318
- 'id, seq, "table" AS referencedTable, "from" AS fromColumn, "to" AS toColumn, on_update AS onUpdate, on_delete AS onDelete, "match" AS matchMode'
1319
- );
1486
+ const rows = await this.run(this.foreignKeysQuery(tableName));
1320
1487
  const byId = /* @__PURE__ */ new Map();
1321
1488
  const ordered = [...rows].sort(
1322
1489
  (a, b) => toNumber(a.id) - toNumber(b.id) || toNumber(a.seq) - toNumber(b.seq)
@@ -1345,7 +1512,7 @@ var CatalogReader = class {
1345
1512
  }
1346
1513
  async sequences() {
1347
1514
  const rows = await this.rows(
1348
- `SELECT name, CAST(seq AS TEXT) AS value FROM ${this.qualifier}.sqlite_sequence`
1515
+ `SELECT name, CAST(seq AS TEXT) AS value FROM ${this.qualifier}sqlite_sequence`
1349
1516
  );
1350
1517
  return rows.map((row) => ({
1351
1518
  tableName: toText(row.name) ?? "",
@@ -1355,7 +1522,7 @@ var CatalogReader = class {
1355
1522
  async settings() {
1356
1523
  const read = async (pragma) => {
1357
1524
  try {
1358
- const rows = await this.rows(`PRAGMA ${this.qualifier}.${pragma}`);
1525
+ const rows = await this.rows(`PRAGMA ${this.qualifier}${pragma}`);
1359
1526
  const first = rows[0];
1360
1527
  return first === void 0 ? void 0 : Object.values(first)[0];
1361
1528
  } catch (error) {
@@ -1904,20 +2071,25 @@ var import_node_buffer = require("buffer");
1904
2071
  function realLiteralExpression(column) {
1905
2072
  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`;
1906
2073
  }
1907
- function columnValueSelect(column, index, utf8Database) {
1908
- const textBranch = utf8Database ? `CAST(${column} AS BLOB)` : column;
1909
- 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} ELSE ${column} END AS "v${index}"`;
2074
+ var NATIVE_VALUE_TRANSPORT = { hexText: false, hexBlob: false };
2075
+ function valueTransport(utf8Database, binaryTransport) {
2076
+ return binaryTransport === "hex" ? { hexText: utf8Database, hexBlob: true } : NATIVE_VALUE_TRANSPORT;
1910
2077
  }
1911
- function asBytes(value) {
1912
- if (Buffer.isBuffer(value)) {
1913
- return value;
2078
+ function columnValueSelect(column, index, utf8Database, transport = NATIVE_VALUE_TRANSPORT) {
2079
+ const textBytes = utf8Database ? `CAST(${column} AS BLOB)` : column;
2080
+ const textBranch = transport.hexText ? `hex(${textBytes})` : textBytes;
2081
+ 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}"`;
2082
+ }
2083
+ function asBytes(value, hex) {
2084
+ if (typeof value === "string") {
2085
+ return Buffer.from(value, hex ? "hex" : "utf8");
1914
2086
  }
1915
- if (value instanceof Uint8Array) {
1916
- return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
2087
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer || Array.isArray(value)) {
2088
+ return toBuffer(value);
1917
2089
  }
1918
2090
  return Buffer.from(String(value), "utf8");
1919
2091
  }
1920
- function renderColumnLiteral(row, index, textStyle) {
2092
+ function renderColumnLiteral(row, index, textStyle, transport = NATIVE_VALUE_TRANSPORT) {
1921
2093
  const storageClass = row[`t${index}`];
1922
2094
  const value = row[`v${index}`] ?? null;
1923
2095
  switch (storageClass) {
@@ -1925,17 +2097,17 @@ function renderColumnLiteral(row, index, textStyle) {
1925
2097
  case "real":
1926
2098
  return String(value);
1927
2099
  case "text": {
1928
- if (typeof value === "string") {
2100
+ if (typeof value === "string" && !transport.hexText) {
1929
2101
  return renderTextLiteral(value, textStyle);
1930
2102
  }
1931
- const bytes = asBytes(value);
2103
+ const bytes = asBytes(value, transport.hexText);
1932
2104
  if ((0, import_node_buffer.isUtf8)(bytes)) {
1933
2105
  return renderTextLiteral(bytes.toString("utf8"), textStyle);
1934
2106
  }
1935
2107
  return Buffer.from(renderTextLiteral(bytes.toString("latin1"), textStyle), "latin1");
1936
2108
  }
1937
2109
  case "blob":
1938
- return renderBlobLiteral(asBytes(value));
2110
+ return renderBlobLiteral(asBytes(value, transport.hexBlob));
1939
2111
  default:
1940
2112
  return "NULL";
1941
2113
  }
@@ -1972,6 +2144,8 @@ async function exportTableDataAsInserts(request) {
1972
2144
  );
1973
2145
  const textStyle = options.rawNewlines ? "raw-newlines" : "escaped";
1974
2146
  const utf8Database = (request.encoding ?? "UTF-8").toUpperCase() === "UTF-8";
2147
+ const features = connectionFeatures(connection);
2148
+ const transport = valueTransport(utf8Database, features.binaryTransport);
1975
2149
  const warnings = [];
1976
2150
  const columns = insertableColumns(table);
1977
2151
  const rowidName = options.preserveRowids ? preservableRowidName(table) : null;
@@ -2003,13 +2177,39 @@ async function exportTableDataAsInserts(request) {
2003
2177
  const columnList = rowidName ? `(${[rowidName, ...columns.map((column) => quoteIdentifierIfNeeded(column.name))].join(",")})` : "";
2004
2178
  const statementPrefix = `INSERT INTO ${quoteIdentifierIfNeeded(table.name)}${columnList} VALUES`;
2005
2179
  const selectList = [
2006
- ...rowidName ? [columnValueSelect(rowidName, 0, utf8Database)] : [],
2180
+ ...rowidName ? [columnValueSelect(rowidName, 0, utf8Database, transport)] : [],
2007
2181
  ...columns.map(
2008
- (column, index) => columnValueSelect(quoteIdentifier(column.name), index + (rowidName ? 1 : 0), utf8Database)
2182
+ (column, index) => columnValueSelect(
2183
+ quoteIdentifier(column.name),
2184
+ index + (rowidName ? 1 : 0),
2185
+ utf8Database,
2186
+ transport
2187
+ )
2009
2188
  )
2010
2189
  ].join(", ");
2011
2190
  const valueCount = columns.length + (rowidName ? 1 : 0);
2012
- const sql = `SELECT ${selectList} FROM ${quoteQualifiedIdentifier(schemaName, table.name)}${systemRowFilterClause(table, request.systemRowFilter)}`;
2191
+ const from = features.schemaQualifiedNames ? quoteQualifiedIdentifier(schemaName, table.name) : quoteIdentifier(table.name);
2192
+ const filter = systemRowFilterCondition(
2193
+ table,
2194
+ request.systemRowFilter,
2195
+ features.reservedNamePrefixes
2196
+ );
2197
+ const rows = features.pagedReadSize === void 0 ? connection.stream(
2198
+ { sql: `SELECT ${selectList} FROM ${from}${filter === null ? "" : ` WHERE ${filter}`}` },
2199
+ {
2200
+ ...signal === void 0 ? {} : { signal },
2201
+ ...options.streamBatchSize === void 0 ? {} : { batchSize: options.streamBatchSize }
2202
+ }
2203
+ ) : readPages({
2204
+ connection,
2205
+ selectList,
2206
+ from,
2207
+ filter,
2208
+ pageSize: features.pagedReadSize,
2209
+ key: pageKey(table, columns, rowidName ? 1 : 0, request.primaryKeyIndex),
2210
+ transport,
2211
+ signal
2212
+ });
2013
2213
  let rowsExported = 0;
2014
2214
  let statementsWritten = 0;
2015
2215
  let output = new SqlChunkBuilder();
@@ -2050,13 +2250,7 @@ async function exportTableDataAsInserts(request) {
2050
2250
  };
2051
2251
  progress("started");
2052
2252
  try {
2053
- for await (const row of connection.stream(
2054
- { sql },
2055
- {
2056
- ...signal === void 0 ? {} : { signal },
2057
- ...options.streamBatchSize === void 0 ? {} : { batchSize: options.streamBatchSize }
2058
- }
2059
- )) {
2253
+ for await (const row of rows) {
2060
2254
  throwIfAborted2(signal);
2061
2255
  const tuple = new SqlChunkBuilder();
2062
2256
  tuple.append("(");
@@ -2064,7 +2258,7 @@ async function exportTableDataAsInserts(request) {
2064
2258
  if (index > 0) {
2065
2259
  tuple.append(",");
2066
2260
  }
2067
- tuple.append(renderColumnLiteral(row, index, textStyle));
2261
+ tuple.append(renderColumnLiteral(row, index, textStyle, transport));
2068
2262
  }
2069
2263
  tuple.append(")");
2070
2264
  if (statement && (statementRows >= maxRowsPerStatement || statement.length + tuple.length + 2 > maxStatementBytes)) {
@@ -2108,16 +2302,121 @@ async function exportTableDataAsInserts(request) {
2108
2302
  throw error;
2109
2303
  }
2110
2304
  }
2111
- function systemRowFilterClause(table, filter) {
2112
- if (filter === void 0 || table.kind !== "system") {
2113
- return "";
2305
+ function systemRowFilterCondition(table, filter, reservedPrefixes) {
2306
+ if (table.kind !== "system" || filter === void 0 && reservedPrefixes.length === 0) {
2307
+ return null;
2114
2308
  }
2115
2309
  const column = table.name === "sqlite_sequence" ? "name" : "tbl";
2116
- const names = [...new Set(filter.map(toAsciiLowerCase))].map(quoteStringLiteral);
2117
- if (names.length === 0) {
2118
- return " WHERE 0";
2310
+ const conditions = [];
2311
+ if (filter !== void 0) {
2312
+ const names = [...new Set(filter.map(toAsciiLowerCase))].map(quoteStringLiteral);
2313
+ if (names.length === 0) {
2314
+ return "0";
2315
+ }
2316
+ conditions.push(`lower(${column}) IN (${names.join(",")})`);
2317
+ }
2318
+ for (const prefix of reservedPrefixes) {
2319
+ const lower = toAsciiLowerCase(prefix);
2320
+ conditions.push(
2321
+ `coalesce(substr(lower(${column}), 1, ${lower.length}) <> ${quoteStringLiteral(lower)}, 1)`
2322
+ );
2119
2323
  }
2120
- return ` WHERE lower(${column}) IN (${names.join(",")})`;
2324
+ return conditions.join(" AND ");
2325
+ }
2326
+ function pageKey(table, columns, valueOffset, primaryKeyIndex) {
2327
+ if (table.kind === "virtual") {
2328
+ return { kind: "offset" };
2329
+ }
2330
+ if (!table.withoutRowid) {
2331
+ if (table.rowidAliasColumn !== null) {
2332
+ return { kind: "rowid", expression: quoteIdentifier(table.rowidAliasColumn) };
2333
+ }
2334
+ const names = new Set(table.columns.map((column) => toAsciiLowerCase(column.name)));
2335
+ const name = ROWID_NAMES.find((candidate) => !names.has(candidate));
2336
+ return name === void 0 ? { kind: "offset" } : { kind: "rowid", expression: name };
2337
+ }
2338
+ const valueIndexes = new Map(
2339
+ columns.map((column, index) => [toAsciiLowerCase(column.name), index + valueOffset])
2340
+ );
2341
+ const keyColumns = primaryKeyIndex && primaryKeyIndex.columns.length > 0 ? primaryKeyIndex.columns.map((column) => ({
2342
+ name: column.name,
2343
+ descending: column.descending,
2344
+ collation: column.collation
2345
+ })) : [...table.columns].filter((column) => column.primaryKeyPosition > 0).sort((a, b) => a.primaryKeyPosition - b.primaryKeyPosition).map((column) => ({ name: column.name, descending: false, collation: null }));
2346
+ const result = [];
2347
+ for (const column of keyColumns) {
2348
+ const valueIndex = column.name === null ? void 0 : valueIndexes.get(toAsciiLowerCase(column.name));
2349
+ if (column.name === null || valueIndex === void 0) {
2350
+ return { kind: "offset" };
2351
+ }
2352
+ const collation = column.collation === null || toAsciiLowerCase(column.collation) === "binary" ? "" : ` COLLATE ${quoteIdentifier(column.collation)}`;
2353
+ result.push({
2354
+ expression: `${quoteIdentifier(column.name)}${collation}`,
2355
+ valueIndex,
2356
+ descending: column.descending
2357
+ });
2358
+ }
2359
+ return result.length === 0 ? { kind: "offset" } : { kind: "primaryKey", columns: result };
2360
+ }
2361
+ var PAGE_KEY_ALIAS = "dbgate_page_key";
2362
+ async function* readPages(request) {
2363
+ const { connection, selectList, from, filter, pageSize, key, transport, signal } = request;
2364
+ let after = null;
2365
+ let offset = 0;
2366
+ for (; ; ) {
2367
+ throwIfAborted2(signal);
2368
+ const conditions = [filter, after].filter((condition) => condition !== null);
2369
+ const where = conditions.length === 0 ? "" : ` WHERE ${conditions.map((c) => `(${c})`).join(" AND ")}`;
2370
+ let sql;
2371
+ if (key.kind === "rowid") {
2372
+ sql = `SELECT ${selectList}, CAST(${key.expression} AS TEXT) AS "${PAGE_KEY_ALIAS}" FROM ${from}${where} ORDER BY ${key.expression} LIMIT ${pageSize}`;
2373
+ } else if (key.kind === "primaryKey") {
2374
+ const order = key.columns.map((column) => `${column.expression}${column.descending ? " DESC" : ""}`).join(", ");
2375
+ sql = `SELECT ${selectList} FROM ${from}${where} ORDER BY ${order} LIMIT ${pageSize}`;
2376
+ } else {
2377
+ sql = `SELECT ${selectList} FROM ${from}${where} LIMIT ${pageSize} OFFSET ${offset}`;
2378
+ }
2379
+ const { rows } = await connection.query({ sql }, signal);
2380
+ for (const row of rows) {
2381
+ yield row;
2382
+ }
2383
+ if (rows.length < pageSize) {
2384
+ return;
2385
+ }
2386
+ const last = rows[rows.length - 1];
2387
+ if (key.kind === "rowid") {
2388
+ after = `${key.expression} > ${String(last[PAGE_KEY_ALIAS])}`;
2389
+ } else if (key.kind === "primaryKey") {
2390
+ after = keyAfterCondition(key.columns, last, transport);
2391
+ } else {
2392
+ offset += rows.length;
2393
+ }
2394
+ }
2395
+ }
2396
+ function keyAfterCondition(columns, row, transport) {
2397
+ const literals = columns.map((column) => keyLiteral(row, column.valueIndex, transport));
2398
+ const alternatives = [];
2399
+ for (let position = 0; position < columns.length; position++) {
2400
+ const terms = [];
2401
+ for (let previous = 0; previous < position; previous++) {
2402
+ terms.push(
2403
+ `${columns[previous].expression} = ${literals[previous]}`
2404
+ );
2405
+ }
2406
+ const column = columns[position];
2407
+ terms.push(`${column.expression} ${column.descending ? "<" : ">"} ${literals[position]}`);
2408
+ alternatives.push(terms.length === 1 ? terms[0] : `(${terms.join(" AND ")})`);
2409
+ }
2410
+ return alternatives.join(" OR ");
2411
+ }
2412
+ function keyLiteral(row, index, transport) {
2413
+ const literal = renderColumnLiteral(row, index, "escaped", transport);
2414
+ if (typeof literal === "string") {
2415
+ return literal;
2416
+ }
2417
+ const value = row[`v${index}`] ?? null;
2418
+ const bytes = typeof value === "string" ? Buffer.from(value, transport.hexText ? "hex" : "utf8") : toBuffer(value);
2419
+ return `CAST(X'${bytes.toString("hex").toUpperCase()}' AS TEXT)`;
2121
2420
  }
2122
2421
 
2123
2422
  // src/compatibility/check.ts
@@ -3726,6 +4025,9 @@ async function dumpSqlite(connectionInput, options, output, onProgress, signal)
3726
4025
  if (!table) {
3727
4026
  return false;
3728
4027
  }
4028
+ const primaryKeyIndex = table.withoutRowid ? introspection.database.indexes.find(
4029
+ (index) => index.origin === "pk" && foldSqliteName(index.tableName) === foldSqliteName(table.name)
4030
+ ) : void 0;
3729
4031
  const result = await exportTableDataAsInserts({
3730
4032
  connection: acquired.connection,
3731
4033
  schemaName: introspection.database.schemaName,
@@ -3733,6 +4035,7 @@ async function dumpSqlite(connectionInput, options, output, onProgress, signal)
3733
4035
  writer,
3734
4036
  encoding: introspection.database.encoding,
3735
4037
  ...entry.systemRowFilter === void 0 ? {} : { systemRowFilter: entry.systemRowFilter },
4038
+ ...primaryKeyIndex === void 0 ? {} : { primaryKeyIndex },
3736
4039
  ...options.dataExport === void 0 ? {} : { options: options.dataExport },
3737
4040
  ...signal === void 0 ? {} : { signal },
3738
4041
  ...onProgress === void 0 ? {} : { onProgress }
@@ -3757,13 +4060,21 @@ async function dumpSqlite(connectionInput, options, output, onProgress, signal)
3757
4060
  onTableData
3758
4061
  });
3759
4062
  onProgress?.({ phase: "finalizing", bytesWritten: renderResult.bytesWritten });
3760
- const sessionWarnings = session.joinedExistingTransaction ? [
3761
- {
4063
+ const sessionWarnings = [];
4064
+ if (session.joinedExistingTransaction) {
4065
+ sessionWarnings.push({
3762
4066
  severity: "info",
3763
4067
  code: "dump-joined-caller-transaction",
3764
4068
  message: "The connection was already inside a transaction, so the dump was read within it and includes that transaction's uncommitted changes."
3765
- }
3766
- ] : [];
4069
+ });
4070
+ }
4071
+ if (session.snapshotUnavailable) {
4072
+ sessionWarnings.push({
4073
+ severity: "warning",
4074
+ code: "snapshot-unavailable",
4075
+ 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."
4076
+ });
4077
+ }
3767
4078
  return {
3768
4079
  bytesWritten: renderResult.bytesWritten,
3769
4080
  renderedDumpIds: renderResult.renderedDumpIds,
@@ -3803,6 +4114,7 @@ function dedupeDiagnostics(diagnostics) {
3803
4114
  DEFENSIVE_WARNING_COMMENT,
3804
4115
  InvalidTextEncodingError,
3805
4116
  MalformedSqlDumpError,
4117
+ NATIVE_VALUE_TRANSPORT,
3806
4118
  OperationCancelledError,
3807
4119
  RestoreError,
3808
4120
  RestoreExecutionError,
@@ -3828,6 +4140,7 @@ function dedupeDiagnostics(diagnostics) {
3828
4140
  classifyWord,
3829
4141
  columnValueSelect,
3830
4142
  completeTransition,
4143
+ connectionFeatures,
3831
4144
  createArchiveIdentity,
3832
4145
  createCanonicalIdentity,
3833
4146
  createDumpId,
@@ -3894,9 +4207,11 @@ function dedupeDiagnostics(diagnostics) {
3894
4207
  throwIfAborted,
3895
4208
  toAsciiLowerCase,
3896
4209
  toAsciiUpperCase,
4210
+ toBuffer,
3897
4211
  toByteStringChunks,
3898
4212
  toNumber,
3899
4213
  toText,
3900
- unsupportedFeatureDiagnostics
4214
+ unsupportedFeatureDiagnostics,
4215
+ valueTransport
3901
4216
  });
3902
4217
  //# sourceMappingURL=index.cjs.map