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.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 Buffer.from(value).toString("utf8");
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 consistency = options?.consistency ?? "snapshot";
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
- const catalog = new CatalogReader(connection, schemaName, capabilities, signal);
903
- const schemaRows = await catalog.schemaRows();
904
- if (schemaRows === null) {
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
- const result = await this.connection.query(
1069
- parameters === void 0 ? { sql } : { sql, parameters },
1070
- this.signal
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
- /** Runs `PRAGMA schema.name(argument)`, as a table-valued function when the library has them. */
1075
- async pragma(name, argument, columns) {
1076
- if (this.capabilities.supportsPragmaFunctions) {
1077
- return this.rows(`SELECT ${columns} FROM pragma_${name}(?, ?)`, [argument, this.schemaName]);
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 this.rows(`PRAGMA ${this.qualifier}.${name}(${quoteStringLiteral(argument)})`);
1157
+ return { sql: `PRAGMA ${this.qualifier}${name}(${quoteStringLiteral(argument)})` };
1080
1158
  }
1081
- async schemaRows() {
1082
- const databases = await this.rows("PRAGMA database_list");
1083
- const known = databases.some(
1084
- (row) => toAsciiLowerCase(toText(row.name) ?? "") === toAsciiLowerCase(this.schemaName)
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
- if (!known) {
1087
- return null;
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}.${master} ORDER BY rowid`
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
- "SELECT name, type, wr, strict FROM pragma_table_list WHERE schema = ?",
1108
- [this.schemaName]
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
- const withHidden = this.capabilities.supportsTableXinfo;
1127
- const rows = await this.pragma(
1128
- withHidden ? "table_xinfo" : "table_info",
1129
- tableName,
1130
- `cid, name, type, "notnull" AS isNotNull, dflt_value AS defaultValue, pk${withHidden ? ", hidden" : ""}`
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.pragma(
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.pragma(
1160
- "index_xinfo",
1161
- name,
1162
- 'seqno, cid, name, "desc" AS isDesc, coll, "key" AS isKey'
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.pragma(
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}.sqlite_sequence`
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}.${pragma}`);
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
- function columnValueSelect(column, index, utf8Database) {
1782
- const textBranch = utf8Database ? `CAST(${column} AS BLOB)` : column;
1783
- 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}"`;
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 asBytes(value) {
1786
- if (Buffer.isBuffer(value)) {
1787
- return value;
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 Buffer.from(value.buffer, value.byteOffset, value.byteLength);
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(quoteIdentifier(column.name), index + (rowidName ? 1 : 0), utf8Database)
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 sql = `SELECT ${selectList} FROM ${quoteQualifiedIdentifier(schemaName, table.name)}${systemRowFilterClause(table, request.systemRowFilter)}`;
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 connection.stream(
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 systemRowFilterClause(table, filter) {
1986
- if (filter === void 0 || table.kind !== "system") {
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 names = [...new Set(filter.map(toAsciiLowerCase))].map(quoteStringLiteral);
1991
- if (names.length === 0) {
1992
- return " WHERE 0";
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 ` WHERE lower(${column}) IN (${names.join(",")})`;
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 = session.joinedExistingTransaction ? [
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