@youtyan/code-viewer 0.2.3 → 0.2.4
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/code-viewer.js +283 -88
- package/package.json +11 -1
- package/web/app.js +219 -46
- package/web/style.css +3 -0
package/dist/code-viewer.js
CHANGED
|
@@ -3884,6 +3884,9 @@ import { spawn as spawn2, spawnSync as spawnSync3 } from "node:child_process";
|
|
|
3884
3884
|
function dockerDatabasesCacheKey(serviceName, kind, cwd) {
|
|
3885
3885
|
return `${serviceName}\x00${kind}\x00${cwd}`;
|
|
3886
3886
|
}
|
|
3887
|
+
function dockerSchemasCacheKey(serviceName, kind, cwd, database) {
|
|
3888
|
+
return `${serviceName}\x00${kind}\x00${cwd}\x00${database}`;
|
|
3889
|
+
}
|
|
3887
3890
|
function setDockerDatabasesCache(key, value, ttlMs, now = Date.now()) {
|
|
3888
3891
|
const cachedValue = [...value];
|
|
3889
3892
|
dockerDatabasesCache.set(key, {
|
|
@@ -3892,6 +3895,14 @@ function setDockerDatabasesCache(key, value, ttlMs, now = Date.now()) {
|
|
|
3892
3895
|
});
|
|
3893
3896
|
return [...cachedValue];
|
|
3894
3897
|
}
|
|
3898
|
+
function setDockerSchemasCache(key, value, ttlMs, now = Date.now()) {
|
|
3899
|
+
const cachedValue = [...value];
|
|
3900
|
+
dockerSchemasCache.set(key, {
|
|
3901
|
+
value: cachedValue,
|
|
3902
|
+
expiresAt: now + ttlMs
|
|
3903
|
+
});
|
|
3904
|
+
return [...cachedValue];
|
|
3905
|
+
}
|
|
3895
3906
|
function fallbackDockerDatabases(defaultDb) {
|
|
3896
3907
|
return defaultDb ? [defaultDb] : [];
|
|
3897
3908
|
}
|
|
@@ -4209,10 +4220,26 @@ function createDockerAdapter(config) {
|
|
|
4209
4220
|
}
|
|
4210
4221
|
const columnCache = new Map;
|
|
4211
4222
|
const tableMetaCache = createTableMetaCache();
|
|
4223
|
+
function currentPostgresSchema() {
|
|
4224
|
+
return config.schema || "public";
|
|
4225
|
+
}
|
|
4226
|
+
function postgresSchemaLiteral() {
|
|
4227
|
+
return escapeSqlString(currentPostgresSchema());
|
|
4228
|
+
}
|
|
4229
|
+
function tableIdentifier(table) {
|
|
4230
|
+
if (config.kind === "postgresql") {
|
|
4231
|
+
return `${sanitizeIdentifier(currentPostgresSchema(), config.kind)}.${sanitizeIdentifier(table, config.kind)}`;
|
|
4232
|
+
}
|
|
4233
|
+
return sanitizeIdentifier(table, config.kind);
|
|
4234
|
+
}
|
|
4235
|
+
function postgresRegclassLiteral(table) {
|
|
4236
|
+
return escapeSqlString(`${sanitizeIdentifier(currentPostgresSchema(), "postgresql")}.${sanitizeIdentifier(table, "postgresql")}`);
|
|
4237
|
+
}
|
|
4212
4238
|
function buildColumnsSql(table) {
|
|
4213
4239
|
const tableLiteral = table.replace(/'/g, "''");
|
|
4214
4240
|
if (config.kind === "postgresql") {
|
|
4215
|
-
|
|
4241
|
+
const schemaLiteral = postgresSchemaLiteral();
|
|
4242
|
+
return `SELECT c.column_name, c.data_type, c.is_nullable, c.column_default, CASE WHEN pk.column_name IS NULL THEN 'NO' ELSE 'YES' END FROM information_schema.columns c LEFT JOIN (SELECT kcu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema AND tc.table_name = kcu.table_name WHERE tc.table_schema = ${schemaLiteral} AND tc.table_name = '${tableLiteral}' AND tc.constraint_type = 'PRIMARY KEY') pk ON pk.column_name = c.column_name WHERE c.table_schema = ${schemaLiteral} AND c.table_name = '${tableLiteral}' ORDER BY c.ordinal_position`;
|
|
4216
4243
|
}
|
|
4217
4244
|
return `SELECT column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${tableLiteral}' ORDER BY ordinal_position`;
|
|
4218
4245
|
}
|
|
@@ -4250,37 +4277,8 @@ function createDockerAdapter(config) {
|
|
|
4250
4277
|
return countResult.rows.length > 0 ? Number(countResult.rows[0][0]) || 0 : 0;
|
|
4251
4278
|
}
|
|
4252
4279
|
function fetchColumnsUncached(table) {
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
sql = `SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '${table.replace(/'/g, "''")}' ORDER BY ordinal_position`;
|
|
4256
|
-
} else {
|
|
4257
|
-
sql = `SELECT column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = '${table.replace(/'/g, "''")}' ORDER BY ordinal_position`;
|
|
4258
|
-
}
|
|
4259
|
-
const result = exec(sql);
|
|
4260
|
-
if (config.kind === "postgresql") {
|
|
4261
|
-
const pkSql = `SELECT a.attname FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = '${table.replace(/'/g, "''")}'::regclass AND i.indisprimary`;
|
|
4262
|
-
let pkCols;
|
|
4263
|
-
try {
|
|
4264
|
-
const pkResult = exec(pkSql);
|
|
4265
|
-
pkCols = new Set(pkResult.rows.map((r) => r[0]));
|
|
4266
|
-
} catch {
|
|
4267
|
-
pkCols = new Set;
|
|
4268
|
-
}
|
|
4269
|
-
return result.rows.map((row) => ({
|
|
4270
|
-
name: row[0],
|
|
4271
|
-
type: row[1],
|
|
4272
|
-
nullable: row[2] === "YES",
|
|
4273
|
-
primaryKey: pkCols.has(row[0]),
|
|
4274
|
-
defaultValue: row[3] === "" ? null : row[3]
|
|
4275
|
-
}));
|
|
4276
|
-
}
|
|
4277
|
-
return result.rows.map((row) => ({
|
|
4278
|
-
name: row[0],
|
|
4279
|
-
type: row[1],
|
|
4280
|
-
nullable: row[2] === "YES",
|
|
4281
|
-
primaryKey: row[4] === "PRI",
|
|
4282
|
-
defaultValue: row[3] === "NULL" ? null : row[3]
|
|
4283
|
-
}));
|
|
4280
|
+
const result = exec(buildColumnsSql(table));
|
|
4281
|
+
return columnsFromInfoRows(result.rows);
|
|
4284
4282
|
}
|
|
4285
4283
|
const adapter = {
|
|
4286
4284
|
kind: config.kind,
|
|
@@ -4289,7 +4287,7 @@ function createDockerAdapter(config) {
|
|
|
4289
4287
|
getTables() {
|
|
4290
4288
|
let sql;
|
|
4291
4289
|
if (config.kind === "postgresql") {
|
|
4292
|
-
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema =
|
|
4290
|
+
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = ${postgresSchemaLiteral()} ORDER BY table_name`;
|
|
4293
4291
|
} else {
|
|
4294
4292
|
sql = `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name`;
|
|
4295
4293
|
}
|
|
@@ -4311,7 +4309,7 @@ function createDockerAdapter(config) {
|
|
|
4311
4309
|
getIndexes() {
|
|
4312
4310
|
let sql;
|
|
4313
4311
|
if (config.kind === "postgresql") {
|
|
4314
|
-
sql = `SELECT indexname, tablename FROM pg_indexes WHERE schemaname =
|
|
4312
|
+
sql = `SELECT indexname, tablename FROM pg_indexes WHERE schemaname = ${postgresSchemaLiteral()} AND indexname NOT LIKE 'pg_%' ORDER BY indexname`;
|
|
4315
4313
|
} else {
|
|
4316
4314
|
sql = `SELECT DISTINCT index_name, table_name, non_unique FROM information_schema.statistics WHERE table_schema = DATABASE() ORDER BY index_name`;
|
|
4317
4315
|
}
|
|
@@ -4334,18 +4332,25 @@ function createDockerAdapter(config) {
|
|
|
4334
4332
|
getForeignKeys() {
|
|
4335
4333
|
let sql;
|
|
4336
4334
|
if (config.kind === "postgresql") {
|
|
4337
|
-
sql = `SELECT tc.table_name, kcu.column_name, ccu.table_name, ccu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema =
|
|
4335
|
+
sql = `SELECT tc.table_schema, tc.table_name, kcu.column_name, ccu.table_schema, ccu.table_name, ccu.column_name FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema JOIN information_schema.constraint_column_usage ccu ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema WHERE tc.constraint_type = 'FOREIGN KEY' AND tc.table_schema = ${postgresSchemaLiteral()}`;
|
|
4338
4336
|
} else {
|
|
4339
4337
|
sql = `SELECT table_name, column_name, referenced_table_name, referenced_column_name FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND referenced_table_name IS NOT NULL`;
|
|
4340
4338
|
}
|
|
4341
4339
|
try {
|
|
4342
4340
|
const result = exec(sql);
|
|
4343
|
-
return result.rows.map((row) =>
|
|
4341
|
+
return result.rows.map((row) => config.kind === "postgresql" ? {
|
|
4342
|
+
fromSchema: row[0],
|
|
4343
|
+
fromTable: row[1],
|
|
4344
|
+
fromColumn: row[2],
|
|
4345
|
+
toSchema: row[3],
|
|
4346
|
+
toTable: row[4],
|
|
4347
|
+
toColumn: row[5]
|
|
4348
|
+
} : {
|
|
4344
4349
|
fromTable: row[0],
|
|
4345
4350
|
fromColumn: row[1],
|
|
4346
4351
|
toTable: row[2],
|
|
4347
4352
|
toColumn: row[3]
|
|
4348
|
-
})
|
|
4353
|
+
});
|
|
4349
4354
|
} catch {
|
|
4350
4355
|
return [];
|
|
4351
4356
|
}
|
|
@@ -4363,7 +4368,7 @@ function createDockerAdapter(config) {
|
|
|
4363
4368
|
let sql;
|
|
4364
4369
|
if (config.kind === "postgresql") {
|
|
4365
4370
|
const inList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
4366
|
-
sql = `SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema =
|
|
4371
|
+
sql = `SELECT table_name, column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema = ${postgresSchemaLiteral()} AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
|
|
4367
4372
|
} else {
|
|
4368
4373
|
const inList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
4369
4374
|
sql = `SELECT table_name, column_name, column_type, is_nullable, column_default, column_key FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name IN (${inList}) ORDER BY table_name, ordinal_position`;
|
|
@@ -4381,7 +4386,7 @@ function createDockerAdapter(config) {
|
|
|
4381
4386
|
if (config.kind === "postgresql") {
|
|
4382
4387
|
try {
|
|
4383
4388
|
const pkInList = uncached.map((t) => `'${t.replace(/'/g, "''")}'`).join(",");
|
|
4384
|
-
const pkResult = exec(`SELECT c.relname, a.attname FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indisprimary AND c.relname IN (${pkInList})`);
|
|
4389
|
+
const pkResult = exec(`SELECT c.relname, a.attname FROM pg_index i JOIN pg_class c ON c.oid = i.indrelid JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indisprimary AND n.nspname = ${postgresSchemaLiteral()} AND c.relname IN (${pkInList})`);
|
|
4385
4390
|
for (const row of pkResult.rows) {
|
|
4386
4391
|
const existing = pkMap.get(row[0]) || new Set;
|
|
4387
4392
|
existing.add(row[1]);
|
|
@@ -4424,7 +4429,7 @@ function createDockerAdapter(config) {
|
|
|
4424
4429
|
return result;
|
|
4425
4430
|
},
|
|
4426
4431
|
getTableRowCount(table) {
|
|
4427
|
-
const id =
|
|
4432
|
+
const id = tableIdentifier(table);
|
|
4428
4433
|
const result = exec(`SELECT COUNT(*) FROM ${id}`);
|
|
4429
4434
|
return result.rows.length > 0 ? Number(result.rows[0][0]) || 0 : 0;
|
|
4430
4435
|
},
|
|
@@ -4433,7 +4438,7 @@ function createDockerAdapter(config) {
|
|
|
4433
4438
|
if (tables.length === 0)
|
|
4434
4439
|
return result;
|
|
4435
4440
|
const parts = tables.map((t) => {
|
|
4436
|
-
const id =
|
|
4441
|
+
const id = tableIdentifier(t);
|
|
4437
4442
|
return `SELECT '${t.replace(/'/g, "''")}' AS tbl, COUNT(*) AS cnt FROM ${id}`;
|
|
4438
4443
|
});
|
|
4439
4444
|
const sql = parts.join(" UNION ALL ");
|
|
@@ -4444,7 +4449,7 @@ function createDockerAdapter(config) {
|
|
|
4444
4449
|
}
|
|
4445
4450
|
} catch {
|
|
4446
4451
|
for (const t of tables) {
|
|
4447
|
-
const id =
|
|
4452
|
+
const id = tableIdentifier(t);
|
|
4448
4453
|
try {
|
|
4449
4454
|
const r = exec(`SELECT COUNT(*) FROM ${id}`);
|
|
4450
4455
|
result.set(t, r.rows.length > 0 ? Number(r.rows[0][0]) || 0 : 0);
|
|
@@ -4456,7 +4461,7 @@ function createDockerAdapter(config) {
|
|
|
4456
4461
|
return result;
|
|
4457
4462
|
},
|
|
4458
4463
|
async getTablePageWithMeta(table, options) {
|
|
4459
|
-
const id =
|
|
4464
|
+
const id = tableIdentifier(table);
|
|
4460
4465
|
const order = buildOrderClause(options.orderBy, config.kind);
|
|
4461
4466
|
const countSql = `SELECT COUNT(*) AS cnt FROM ${id}`;
|
|
4462
4467
|
const columnsPromise = tableMetaCache.getColumns(table, () => fetchColumnsAsyncUncached(table));
|
|
@@ -4471,7 +4476,7 @@ function createDockerAdapter(config) {
|
|
|
4471
4476
|
return tablePageMetaFromResults(columns, dataResult, totalRows);
|
|
4472
4477
|
},
|
|
4473
4478
|
async getFilteredTablePageWithMeta(table, options) {
|
|
4474
|
-
const id =
|
|
4479
|
+
const id = tableIdentifier(table);
|
|
4475
4480
|
const order = buildOrderClause(options.orderBy, config.kind);
|
|
4476
4481
|
const where = buildDockerFilterWhere(options.grouped, config.kind);
|
|
4477
4482
|
const whereClause = where ? ` WHERE ${where}` : "";
|
|
@@ -4488,7 +4493,7 @@ function createDockerAdapter(config) {
|
|
|
4488
4493
|
return tablePageMetaFromResults(columns, dataResult, rowCountFromResult(countResult));
|
|
4489
4494
|
},
|
|
4490
4495
|
getTablePage(table, options) {
|
|
4491
|
-
const id =
|
|
4496
|
+
const id = tableIdentifier(table);
|
|
4492
4497
|
const order = buildOrderClause(options.orderBy, config.kind);
|
|
4493
4498
|
const cols = this.getColumns(table);
|
|
4494
4499
|
const selectList = buildTableSelectList(cols, config.kind);
|
|
@@ -4522,10 +4527,8 @@ function createDockerAdapter(config) {
|
|
|
4522
4527
|
if (BLOCKED_RE.test(upper)) {
|
|
4523
4528
|
throw new Error("Query contains a disallowed statement keyword");
|
|
4524
4529
|
}
|
|
4525
|
-
const readOnlyPreamble = config.kind === "postgresql" ? "BEGIN TRANSACTION READ ONLY; " : "SET SESSION TRANSACTION READ ONLY; ";
|
|
4526
|
-
const readOnlyPostamble = config.kind === "postgresql" ? "; COMMIT" : "; SET SESSION TRANSACTION READ WRITE";
|
|
4527
4530
|
const stripped = trimmed.replace(/;\s*$/, "");
|
|
4528
|
-
const limited =
|
|
4531
|
+
const limited = config.kind === "postgresql" ? `BEGIN TRANSACTION READ ONLY; SET LOCAL search_path = ${sanitizeIdentifier(currentPostgresSchema(), config.kind)}; ${stripped} LIMIT ${maxRows}; COMMIT` : `SET SESSION TRANSACTION READ ONLY; ${stripped} LIMIT ${maxRows}; SET SESSION TRANSACTION READ WRITE`;
|
|
4529
4532
|
const result = exec(limited);
|
|
4530
4533
|
const columnNames = config.kind === "mysql" && result.columns.length > 0 ? result.columns : result.rows.length > 0 ? Array.from({ length: result.rows[0].length }, (_, i) => `col${i + 1}`) : [];
|
|
4531
4534
|
return {
|
|
@@ -4541,14 +4544,14 @@ function createDockerAdapter(config) {
|
|
|
4541
4544
|
getCreateStatement(table) {
|
|
4542
4545
|
if (config.kind === "mysql") {
|
|
4543
4546
|
try {
|
|
4544
|
-
const result = exec(`SHOW CREATE TABLE ${
|
|
4547
|
+
const result = exec(`SHOW CREATE TABLE ${tableIdentifier(table)}`);
|
|
4545
4548
|
return result.rows.length > 0 ? result.rows[0][1] || "" : "";
|
|
4546
4549
|
} catch {
|
|
4547
4550
|
return "";
|
|
4548
4551
|
}
|
|
4549
4552
|
}
|
|
4550
4553
|
try {
|
|
4551
|
-
const result = exec(`SELECT 'CREATE TABLE ' ||
|
|
4554
|
+
const result = exec(`SELECT 'CREATE TABLE ' || ${escapeSqlString(tableIdentifier(table))} || ' (...)' AS ddl`);
|
|
4552
4555
|
return result.rows.length > 0 ? result.rows[0][0] || "" : "";
|
|
4553
4556
|
} catch {
|
|
4554
4557
|
return "";
|
|
@@ -4559,7 +4562,7 @@ function createDockerAdapter(config) {
|
|
|
4559
4562
|
if (config.kind === "mysql") {
|
|
4560
4563
|
sql = `SELECT trigger_name, action_statement FROM information_schema.triggers WHERE event_object_schema = DATABASE() AND event_object_table = '${table.replace(/'/g, "''")}'`;
|
|
4561
4564
|
} else {
|
|
4562
|
-
sql = `SELECT tgname, pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid =
|
|
4565
|
+
sql = `SELECT tgname, pg_get_triggerdef(oid) FROM pg_trigger WHERE tgrelid = ${postgresRegclassLiteral(table)}::regclass AND NOT tgisinternal`;
|
|
4563
4566
|
}
|
|
4564
4567
|
try {
|
|
4565
4568
|
const result = exec(sql);
|
|
@@ -4617,7 +4620,7 @@ function listDockerDatabases(serviceName, kind, env, cwd) {
|
|
|
4617
4620
|
return [...cached.value];
|
|
4618
4621
|
const containerName = resolveRunningComposeContainerName(serviceName, cwd);
|
|
4619
4622
|
if (!containerName) {
|
|
4620
|
-
return
|
|
4623
|
+
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
4621
4624
|
}
|
|
4622
4625
|
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || env.POSTGRES_USERNAME || env.MYSQL_USERNAME || env.USER || "root";
|
|
4623
4626
|
const password = env.POSTGRES_PASSWORD || env.MYSQL_PASSWORD || env.MARIADB_PASSWORD || "";
|
|
@@ -4654,7 +4657,43 @@ function listDockerDatabases(serviceName, kind, env, cwd) {
|
|
|
4654
4657
|
return setDockerDatabasesCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
4655
4658
|
}
|
|
4656
4659
|
}
|
|
4657
|
-
function
|
|
4660
|
+
function listDockerSchemas(serviceName, kind, env, cwd, overrideDatabase) {
|
|
4661
|
+
if (kind !== "postgresql")
|
|
4662
|
+
return [];
|
|
4663
|
+
const user = env.POSTGRES_USER || env.POSTGRES_USERNAME || "postgres";
|
|
4664
|
+
const password = env.POSTGRES_PASSWORD || "";
|
|
4665
|
+
const database = overrideDatabase || env.POSTGRES_DB || "postgres";
|
|
4666
|
+
const cacheKey = dockerSchemasCacheKey(serviceName, kind, cwd, database);
|
|
4667
|
+
const now = Date.now();
|
|
4668
|
+
const cached = dockerSchemasCache.get(cacheKey);
|
|
4669
|
+
if (cached && cached.expiresAt > now)
|
|
4670
|
+
return [...cached.value];
|
|
4671
|
+
const containerName = resolveRunningComposeContainerName(serviceName, cwd);
|
|
4672
|
+
if (!containerName) {
|
|
4673
|
+
return setDockerSchemasCache(cacheKey, [], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
4674
|
+
}
|
|
4675
|
+
const config = {
|
|
4676
|
+
kind,
|
|
4677
|
+
containerName,
|
|
4678
|
+
user,
|
|
4679
|
+
password,
|
|
4680
|
+
database
|
|
4681
|
+
};
|
|
4682
|
+
try {
|
|
4683
|
+
const sql = `SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema') AND schema_name NOT LIKE 'pg_toast%' AND schema_name NOT LIKE 'pg_temp_%' AND schema_name NOT LIKE 'pg_toast_temp_%' AND has_schema_privilege(schema_name, 'USAGE') ORDER BY CASE WHEN schema_name = 'public' THEN 0 ELSE 1 END, schema_name`;
|
|
4684
|
+
const result = execInContainer(config, sql);
|
|
4685
|
+
if (result.code !== 0) {
|
|
4686
|
+
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
4687
|
+
}
|
|
4688
|
+
const parsed = parseTsvOutput(result.stdout, false);
|
|
4689
|
+
const schemas = parsed.rows.map((r) => r[0]).filter(Boolean);
|
|
4690
|
+
const value = schemas.length > 0 ? schemas : ["public"];
|
|
4691
|
+
return setDockerSchemasCache(cacheKey, value, DOCKER_DATABASES_POSITIVE_TTL_MS, now);
|
|
4692
|
+
} catch {
|
|
4693
|
+
return setDockerSchemasCache(cacheKey, ["public"], DOCKER_DATABASES_NEGATIVE_TTL_MS, now);
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
function openDockerAdapter(serviceName, kind, env, cwd, overrideDatabase, schema) {
|
|
4658
4697
|
const containerName = resolveRunningComposeContainerNameOrThrow(serviceName, cwd);
|
|
4659
4698
|
const user = env.POSTGRES_USER || env.MYSQL_USER || env.MARIADB_USER || (kind === "postgresql" ? "postgres" : "root");
|
|
4660
4699
|
const password = env.POSTGRES_PASSWORD || env.MYSQL_PASSWORD || env.MYSQL_ROOT_PASSWORD || env.MARIADB_PASSWORD || env.MARIADB_ROOT_PASSWORD || "";
|
|
@@ -4664,14 +4703,16 @@ function openDockerAdapter(serviceName, kind, env, cwd, overrideDatabase) {
|
|
|
4664
4703
|
containerName,
|
|
4665
4704
|
user,
|
|
4666
4705
|
password,
|
|
4667
|
-
database
|
|
4706
|
+
database,
|
|
4707
|
+
...kind === "postgresql" && schema ? { schema } : {}
|
|
4668
4708
|
});
|
|
4669
4709
|
}
|
|
4670
|
-
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, spawnSyncImpl2, MYSQL_SPATIAL_TYPES;
|
|
4710
|
+
var COLUMNS_TTL_MS = 30000, ROWCOUNT_TTL_MS = 15000, DOCKER_DATABASES_POSITIVE_TTL_MS = 15000, DOCKER_DATABASES_NEGATIVE_TTL_MS = 3000, dockerDatabasesCache, dockerSchemasCache, spawnSyncImpl2, MYSQL_SPATIAL_TYPES;
|
|
4671
4711
|
var init_docker = __esm(() => {
|
|
4672
4712
|
init_sql_snapshot();
|
|
4673
4713
|
init_docker_utils();
|
|
4674
4714
|
dockerDatabasesCache = new Map;
|
|
4715
|
+
dockerSchemasCache = new Map;
|
|
4675
4716
|
spawnSyncImpl2 = spawnSync3;
|
|
4676
4717
|
MYSQL_SPATIAL_TYPES = new Set([
|
|
4677
4718
|
"geometry",
|
|
@@ -5957,6 +5998,12 @@ function createDockerAdapterCache(maxEntries = DEFAULT_MAX_DOCKER_ADAPTER_CACHE,
|
|
|
5957
5998
|
const cached = cache.get(key);
|
|
5958
5999
|
if (cached)
|
|
5959
6000
|
closeEntry(key, cached);
|
|
6001
|
+
},
|
|
6002
|
+
closePrefix(prefix) {
|
|
6003
|
+
for (const [key, entry] of Array.from(cache)) {
|
|
6004
|
+
if (key.startsWith(prefix))
|
|
6005
|
+
closeEntry(key, entry);
|
|
6006
|
+
}
|
|
5960
6007
|
}
|
|
5961
6008
|
};
|
|
5962
6009
|
}
|
|
@@ -7049,12 +7096,18 @@ function deleteQueryHistoryEntry(state, id) {
|
|
|
7049
7096
|
entries: state.entries.filter((e) => e.id !== id)
|
|
7050
7097
|
};
|
|
7051
7098
|
}
|
|
7052
|
-
function clearQueryHistory(state, dbId) {
|
|
7099
|
+
function clearQueryHistory(state, dbId, schema) {
|
|
7053
7100
|
if (!dbId)
|
|
7054
7101
|
return emptyState();
|
|
7055
7102
|
return {
|
|
7056
7103
|
version: 1,
|
|
7057
|
-
entries: state.entries.filter((e) =>
|
|
7104
|
+
entries: state.entries.filter((e) => {
|
|
7105
|
+
if (e.dbId !== dbId)
|
|
7106
|
+
return true;
|
|
7107
|
+
if (schema === undefined)
|
|
7108
|
+
return false;
|
|
7109
|
+
return (e.schema || "public") !== schema;
|
|
7110
|
+
})
|
|
7058
7111
|
};
|
|
7059
7112
|
}
|
|
7060
7113
|
var CODE_VIEWER_DIR2 = ".code-viewer", HISTORY_FILE_NAME = "query-history.json", MAX_ENTRIES2 = 200, MAX_PREVIEW_ROWS = 100, MAX_JSON_BYTES = 1e6;
|
|
@@ -7095,6 +7148,9 @@ async function getStoreDb(cwd) {
|
|
|
7095
7148
|
storeDb.exec("PRAGMA journal_mode=WAL");
|
|
7096
7149
|
storeDb.exec("PRAGMA foreign_keys=ON");
|
|
7097
7150
|
storeDb.exec(SCHEMA_SQL);
|
|
7151
|
+
try {
|
|
7152
|
+
storeDb.exec("ALTER TABLE snapshots ADD COLUMN schema_name TEXT");
|
|
7153
|
+
} catch {}
|
|
7098
7154
|
return storeDb;
|
|
7099
7155
|
}
|
|
7100
7156
|
function makeId2(prefix) {
|
|
@@ -7103,10 +7159,10 @@ function makeId2(prefix) {
|
|
|
7103
7159
|
function hashPayload(payloadJson) {
|
|
7104
7160
|
return createHash4("sha256").update(payloadJson).digest("hex");
|
|
7105
7161
|
}
|
|
7106
|
-
async function createSnapshot(cwd, dbId, kind, tables, note) {
|
|
7162
|
+
async function createSnapshot(cwd, dbId, kind, tables, note, schema) {
|
|
7107
7163
|
const db = await getStoreDb(cwd);
|
|
7108
7164
|
const id = makeId2("snap");
|
|
7109
|
-
db.prepare("INSERT INTO snapshots (id, db_id, kind, note, created_at, status) VALUES (?, ?, ?, ?, ?, ?)").run(id, dbId, kind, note, new Date().toISOString(), "running");
|
|
7165
|
+
db.prepare("INSERT INTO snapshots (id, db_id, schema_name, kind, note, created_at, status) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, dbId, schema ?? null, kind, note, new Date().toISOString(), "running");
|
|
7110
7166
|
for (const t of tables) {
|
|
7111
7167
|
db.prepare("INSERT INTO snapshot_tables (snapshot_id, table_name) VALUES (?, ?)").run(id, t);
|
|
7112
7168
|
}
|
|
@@ -7135,19 +7191,22 @@ async function finalizeSnapshot(cwd, snapshotId, error) {
|
|
|
7135
7191
|
db.prepare("UPDATE snapshots SET status = 'done' WHERE id = ?").run(snapshotId);
|
|
7136
7192
|
}
|
|
7137
7193
|
}
|
|
7138
|
-
async function listSnapshots(cwd, dbId) {
|
|
7194
|
+
async function listSnapshots(cwd, dbId, schema) {
|
|
7139
7195
|
const db = await getStoreDb(cwd);
|
|
7140
7196
|
let rows;
|
|
7141
|
-
if (dbId) {
|
|
7142
|
-
rows = db.prepare("SELECT id, db_id, kind, note, created_at, status, error_message FROM snapshots WHERE db_id = ? ORDER BY created_at DESC").all(dbId);
|
|
7197
|
+
if (dbId && schema !== undefined) {
|
|
7198
|
+
rows = db.prepare("SELECT id, db_id, schema_name, kind, note, created_at, status, error_message FROM snapshots WHERE db_id = ? AND COALESCE(schema_name, 'public') = ? ORDER BY created_at DESC").all(dbId, schema);
|
|
7199
|
+
} else if (dbId) {
|
|
7200
|
+
rows = db.prepare("SELECT id, db_id, schema_name, kind, note, created_at, status, error_message FROM snapshots WHERE db_id = ? ORDER BY created_at DESC").all(dbId);
|
|
7143
7201
|
} else {
|
|
7144
|
-
rows = db.prepare("SELECT id, db_id, kind, note, created_at, status, error_message FROM snapshots ORDER BY created_at DESC").all();
|
|
7202
|
+
rows = db.prepare("SELECT id, db_id, schema_name, kind, note, created_at, status, error_message FROM snapshots ORDER BY created_at DESC").all();
|
|
7145
7203
|
}
|
|
7146
7204
|
return rows.map((r) => {
|
|
7147
7205
|
const tableRows = db.prepare("SELECT table_name FROM snapshot_tables WHERE snapshot_id = ?").all(r.id);
|
|
7148
7206
|
return {
|
|
7149
7207
|
id: r.id,
|
|
7150
7208
|
dbId: r.db_id,
|
|
7209
|
+
...r.schema_name ? { schema: r.schema_name } : {},
|
|
7151
7210
|
kind: r.kind,
|
|
7152
7211
|
note: r.note,
|
|
7153
7212
|
createdAt: r.created_at,
|
|
@@ -7172,8 +7231,22 @@ async function deleteSnapshot(cwd, snapshotId) {
|
|
|
7172
7231
|
}
|
|
7173
7232
|
}
|
|
7174
7233
|
}
|
|
7234
|
+
function getSnapshotScope(db, snapshotId) {
|
|
7235
|
+
const row = db.prepare("SELECT db_id, COALESCE(schema_name, 'public') AS schema_name FROM snapshots WHERE id = ?").get(snapshotId);
|
|
7236
|
+
if (!row)
|
|
7237
|
+
throw new Error(`snapshot not found: ${snapshotId}`);
|
|
7238
|
+
return { dbId: row.db_id, schema: row.schema_name };
|
|
7239
|
+
}
|
|
7240
|
+
function assertSameSnapshotScope(db, beforeId, afterId) {
|
|
7241
|
+
const before = getSnapshotScope(db, beforeId);
|
|
7242
|
+
const after = getSnapshotScope(db, afterId);
|
|
7243
|
+
if (before.dbId !== after.dbId || before.schema !== after.schema) {
|
|
7244
|
+
throw new Error(`cannot compare snapshots from different database/schema (${before.dbId}:${before.schema} vs ${after.dbId}:${after.schema})`);
|
|
7245
|
+
}
|
|
7246
|
+
}
|
|
7175
7247
|
async function computeDiffTables(cwd, beforeId, afterId) {
|
|
7176
7248
|
const db = await getStoreDb(cwd);
|
|
7249
|
+
assertSameSnapshotScope(db, beforeId, afterId);
|
|
7177
7250
|
const beforeTables = db.prepare("SELECT table_name, table_hash, row_count FROM snapshot_tables WHERE snapshot_id = ?").all(beforeId);
|
|
7178
7251
|
const afterTables = db.prepare("SELECT table_name, table_hash, row_count FROM snapshot_tables WHERE snapshot_id = ?").all(afterId);
|
|
7179
7252
|
const beforeMap = new Map(beforeTables.map((t) => [t.table_name, t]));
|
|
@@ -7196,7 +7269,7 @@ async function computeDiffTables(cwd, beforeId, afterId) {
|
|
|
7196
7269
|
if (!b) {
|
|
7197
7270
|
results.push({
|
|
7198
7271
|
tableName: table,
|
|
7199
|
-
insertedCount: a.row_count,
|
|
7272
|
+
insertedCount: a ? a.row_count : 0,
|
|
7200
7273
|
updatedCount: 0,
|
|
7201
7274
|
deletedCount: 0,
|
|
7202
7275
|
unchangedCount: 0
|
|
@@ -7245,6 +7318,7 @@ async function computeDiffTables(cwd, beforeId, afterId) {
|
|
|
7245
7318
|
}
|
|
7246
7319
|
async function computeDiffRows(cwd, beforeId, afterId, table, offset = 0, limit = 200) {
|
|
7247
7320
|
const db = await getStoreDb(cwd);
|
|
7321
|
+
assertSameSnapshotScope(db, beforeId, afterId);
|
|
7248
7322
|
const allDiffRows = [];
|
|
7249
7323
|
const inserted = db.prepare(`SELECT a.row_key_json, a.payload_hash
|
|
7250
7324
|
FROM snapshot_rows a
|
|
@@ -7314,6 +7388,7 @@ var CODE_VIEWER_DIR3 = ".code-viewer", SNAPSHOT_DB_NAME = "db-snapshots.sqlite",
|
|
|
7314
7388
|
CREATE TABLE IF NOT EXISTS snapshots (
|
|
7315
7389
|
id TEXT PRIMARY KEY,
|
|
7316
7390
|
db_id TEXT NOT NULL,
|
|
7391
|
+
schema_name TEXT,
|
|
7317
7392
|
kind TEXT NOT NULL,
|
|
7318
7393
|
note TEXT NOT NULL DEFAULT '',
|
|
7319
7394
|
created_at TEXT NOT NULL,
|
|
@@ -7367,7 +7442,7 @@ async function runSnapshot(cwd, source, dbId, containers, note, onProgress, opti
|
|
|
7367
7442
|
throw new Error("data source does not support snapshot (missing SnapshotIterable capability)");
|
|
7368
7443
|
}
|
|
7369
7444
|
const snapshotSource = source;
|
|
7370
|
-
const snapshotId = await createSnapshot(cwd, dbId, snapshotSource.kind, containers, note);
|
|
7445
|
+
const snapshotId = await createSnapshot(cwd, dbId, snapshotSource.kind, containers, note, options.schema);
|
|
7371
7446
|
try {
|
|
7372
7447
|
options.onSnapshotId?.(snapshotId);
|
|
7373
7448
|
for (const container of containers) {
|
|
@@ -7600,14 +7675,40 @@ function ensureInit() {
|
|
|
7600
7675
|
async function getAdapter(r, _cwd) {
|
|
7601
7676
|
if (r.docker) {
|
|
7602
7677
|
const docker = r.docker;
|
|
7603
|
-
|
|
7678
|
+
const cacheKey = r.schema ? `${r.dbId}\x00schema=${r.schema}` : r.dbId;
|
|
7679
|
+
return dockerAdapterCache.getOrOpen(cacheKey, () => openDockerAdapter(docker.serviceName, docker.kind, docker.env, docker.composeDir, docker.database, r.schema));
|
|
7604
7680
|
}
|
|
7605
7681
|
return getConnection(r.resolved);
|
|
7606
7682
|
}
|
|
7607
7683
|
function sanitizeFilename(name) {
|
|
7608
7684
|
return name.replace(/["\\\r\n\x00-\x1f]/g, "_");
|
|
7609
7685
|
}
|
|
7610
|
-
function
|
|
7686
|
+
function normalizeSchemaParam(value) {
|
|
7687
|
+
if (value === undefined || value === null || value === "")
|
|
7688
|
+
return;
|
|
7689
|
+
if (value.length > MAX_SCHEMA_NAME_LEN) {
|
|
7690
|
+
return textError("invalid schema parameter", 400);
|
|
7691
|
+
}
|
|
7692
|
+
if (/[\x00-\x1f\x7f]/.test(value)) {
|
|
7693
|
+
return textError("invalid schema parameter", 400);
|
|
7694
|
+
}
|
|
7695
|
+
return value;
|
|
7696
|
+
}
|
|
7697
|
+
function resolvePostgresSchema(info, requestedSchema) {
|
|
7698
|
+
if (info.kind !== "postgresql")
|
|
7699
|
+
return;
|
|
7700
|
+
const schemas = listDockerSchemas(info.serviceName, "postgresql", info.env, info.composeDir, info.database);
|
|
7701
|
+
if (requestedSchema) {
|
|
7702
|
+
if (!schemas.includes(requestedSchema)) {
|
|
7703
|
+
return textError(`schema not found: ${requestedSchema}`, 404);
|
|
7704
|
+
}
|
|
7705
|
+
return requestedSchema;
|
|
7706
|
+
}
|
|
7707
|
+
if (schemas.includes("public"))
|
|
7708
|
+
return "public";
|
|
7709
|
+
return schemas[0] || "public";
|
|
7710
|
+
}
|
|
7711
|
+
function resolveDb(cwd, dbParam, omitDirNames, schemaParam) {
|
|
7611
7712
|
if (!dbParam)
|
|
7612
7713
|
return textError("missing db parameter", 400);
|
|
7613
7714
|
if (dbParam.startsWith("docker:")) {
|
|
@@ -7624,7 +7725,18 @@ function resolveDb(cwd, dbParam, omitDirNames) {
|
|
|
7624
7725
|
return textError("elasticsearch services must use the /_db/elasticsearch/* routes", 400);
|
|
7625
7726
|
}
|
|
7626
7727
|
const resolved2 = parsed.database ? { ...info, database: parsed.database } : info;
|
|
7627
|
-
|
|
7728
|
+
const requestedSchema = normalizeSchemaParam(schemaParam);
|
|
7729
|
+
if (requestedSchema instanceof Response)
|
|
7730
|
+
return requestedSchema;
|
|
7731
|
+
const schema = resolvePostgresSchema(resolved2, requestedSchema);
|
|
7732
|
+
if (schema instanceof Response)
|
|
7733
|
+
return schema;
|
|
7734
|
+
return {
|
|
7735
|
+
resolved: dbParam,
|
|
7736
|
+
dbId: dbParam,
|
|
7737
|
+
docker: resolved2,
|
|
7738
|
+
...schema ? { schema } : {}
|
|
7739
|
+
};
|
|
7628
7740
|
}
|
|
7629
7741
|
const resolved = validateDbPath(cwd, dbParam);
|
|
7630
7742
|
if (!resolved)
|
|
@@ -7679,8 +7791,24 @@ function handleFiles(cwd, omitDirNames) {
|
|
|
7679
7791
|
};
|
|
7680
7792
|
return json(body);
|
|
7681
7793
|
}
|
|
7794
|
+
function handleSchemas(cwd, url, omitDirNames) {
|
|
7795
|
+
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
|
|
7796
|
+
if (r instanceof Response)
|
|
7797
|
+
return r;
|
|
7798
|
+
if (!r.docker || r.docker.kind !== "postgresql") {
|
|
7799
|
+
const body2 = { dbId: r.dbId, schemas: [] };
|
|
7800
|
+
return json(body2);
|
|
7801
|
+
}
|
|
7802
|
+
const schemas = listDockerSchemas(r.docker.serviceName, "postgresql", r.docker.env, r.docker.composeDir, r.docker.database);
|
|
7803
|
+
const body = {
|
|
7804
|
+
dbId: r.dbId,
|
|
7805
|
+
schemas: schemas.map((name) => ({ name })),
|
|
7806
|
+
selectedSchema: r.schema
|
|
7807
|
+
};
|
|
7808
|
+
return json(body);
|
|
7809
|
+
}
|
|
7682
7810
|
async function handleSchema(cwd, url, omitDirNames) {
|
|
7683
|
-
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
|
|
7811
|
+
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
|
|
7684
7812
|
if (r instanceof Response)
|
|
7685
7813
|
return r;
|
|
7686
7814
|
const includeColumns = url.searchParams.get("includeColumns") === "1";
|
|
@@ -7705,6 +7833,7 @@ async function handleSchema(cwd, url, omitDirNames) {
|
|
|
7705
7833
|
const foreignKeys = adapter.getForeignKeys();
|
|
7706
7834
|
const body = {
|
|
7707
7835
|
dbId: r.dbId,
|
|
7836
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7708
7837
|
tables: tablesWithCount,
|
|
7709
7838
|
indexes,
|
|
7710
7839
|
foreignKeys
|
|
@@ -7782,7 +7911,7 @@ function groupFiltersByValue(filters) {
|
|
|
7782
7911
|
return grouped;
|
|
7783
7912
|
}
|
|
7784
7913
|
async function handleTable(cwd, url, omitDirNames) {
|
|
7785
|
-
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
|
|
7914
|
+
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
|
|
7786
7915
|
if (r instanceof Response)
|
|
7787
7916
|
return r;
|
|
7788
7917
|
const table = url.searchParams.get("table");
|
|
@@ -7817,6 +7946,7 @@ async function handleTable(cwd, url, omitDirNames) {
|
|
|
7817
7946
|
}
|
|
7818
7947
|
const body2 = {
|
|
7819
7948
|
dbId: r.dbId,
|
|
7949
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7820
7950
|
table,
|
|
7821
7951
|
columns: meta.columns,
|
|
7822
7952
|
rows: serializeDbRows(meta.rows),
|
|
@@ -7839,6 +7969,7 @@ async function handleTable(cwd, url, omitDirNames) {
|
|
|
7839
7969
|
}
|
|
7840
7970
|
const body2 = {
|
|
7841
7971
|
dbId: r.dbId,
|
|
7972
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7842
7973
|
table,
|
|
7843
7974
|
columns: meta.columns,
|
|
7844
7975
|
rows: serializeDbRows(meta.rows),
|
|
@@ -7870,6 +8001,7 @@ async function handleTable(cwd, url, omitDirNames) {
|
|
|
7870
8001
|
const dataResult = adapter.executeReadonlyQuery(dataSql, filter.useParams ? [...filter.params, limit, offset] : undefined);
|
|
7871
8002
|
const body2 = {
|
|
7872
8003
|
dbId: r.dbId,
|
|
8004
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7873
8005
|
table,
|
|
7874
8006
|
columns,
|
|
7875
8007
|
rows: serializeDbRows(dataResult.rows),
|
|
@@ -7885,6 +8017,7 @@ async function handleTable(cwd, url, omitDirNames) {
|
|
|
7885
8017
|
const totalRows = result.rowCount < limit ? offset + result.rowCount : adapter.getTableRowCount(table);
|
|
7886
8018
|
const body = {
|
|
7887
8019
|
dbId: r.dbId,
|
|
8020
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7888
8021
|
table,
|
|
7889
8022
|
columns,
|
|
7890
8023
|
rows: serializeDbRows(result.rows),
|
|
@@ -7937,7 +8070,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
|
|
|
7937
8070
|
return body;
|
|
7938
8071
|
if (!body.db || !body.sql)
|
|
7939
8072
|
return textError("missing db or sql", 400);
|
|
7940
|
-
const r = resolveDb(cwd, body.db, omitDirNames);
|
|
8073
|
+
const r = resolveDb(cwd, body.db, omitDirNames, body.schema);
|
|
7941
8074
|
if (r instanceof Response)
|
|
7942
8075
|
return r;
|
|
7943
8076
|
const maxRows = Math.min(1e4, Math.max(1, body.maxRows || 1000));
|
|
@@ -7952,6 +8085,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
|
|
|
7952
8085
|
const columnTypes = inferredColumns.length > 0 ? inferredColumns.map((col) => col.type) : result.columnTypes;
|
|
7953
8086
|
const response = {
|
|
7954
8087
|
dbId: body.db,
|
|
8088
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7955
8089
|
columns,
|
|
7956
8090
|
columnTypes,
|
|
7957
8091
|
rows: serializedRows,
|
|
@@ -7963,6 +8097,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
|
|
|
7963
8097
|
const entry = {
|
|
7964
8098
|
id: makeHistoryId(),
|
|
7965
8099
|
dbId: body.db,
|
|
8100
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7966
8101
|
sql: body.sql,
|
|
7967
8102
|
title: body.title,
|
|
7968
8103
|
body: body.body,
|
|
@@ -7979,7 +8114,12 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
|
|
|
7979
8114
|
const state = loadQueryHistory(cwd);
|
|
7980
8115
|
const updated = addQueryHistoryEntry(state, entry);
|
|
7981
8116
|
saveQueryHistory(cwd, updated);
|
|
7982
|
-
sendSse?.("db-query", JSON.stringify({
|
|
8117
|
+
sendSse?.("db-query", JSON.stringify({
|
|
8118
|
+
action: "add",
|
|
8119
|
+
dbId: body.db,
|
|
8120
|
+
schema: r.schema,
|
|
8121
|
+
id: entry.id
|
|
8122
|
+
}));
|
|
7983
8123
|
}
|
|
7984
8124
|
return json(response);
|
|
7985
8125
|
} catch (err) {
|
|
@@ -7990,6 +8130,7 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
|
|
|
7990
8130
|
const elapsed = Date.now() - start;
|
|
7991
8131
|
const response = {
|
|
7992
8132
|
dbId: body.db,
|
|
8133
|
+
...r.schema ? { schema: r.schema } : {},
|
|
7993
8134
|
columns: [],
|
|
7994
8135
|
columnTypes: [],
|
|
7995
8136
|
rows: [],
|
|
@@ -8003,11 +8144,20 @@ async function handleQuery(cwd, req, sendSse, omitDirNames) {
|
|
|
8003
8144
|
}
|
|
8004
8145
|
function handleHistory(cwd, url) {
|
|
8005
8146
|
const dbId = url.searchParams.get("db") || undefined;
|
|
8147
|
+
const schema = normalizeSchemaParam(url.searchParams.get("schema"));
|
|
8148
|
+
if (schema instanceof Response)
|
|
8149
|
+
return schema;
|
|
8006
8150
|
const state = loadQueryHistory(cwd);
|
|
8007
8151
|
if (dbId) {
|
|
8008
8152
|
return json({
|
|
8009
8153
|
version: 1,
|
|
8010
|
-
entries: state.entries.filter((e) =>
|
|
8154
|
+
entries: state.entries.filter((e) => {
|
|
8155
|
+
if (e.dbId !== dbId)
|
|
8156
|
+
return false;
|
|
8157
|
+
if (schema === undefined)
|
|
8158
|
+
return true;
|
|
8159
|
+
return (e.schema || "public") === schema;
|
|
8160
|
+
})
|
|
8011
8161
|
});
|
|
8012
8162
|
}
|
|
8013
8163
|
return json(state);
|
|
@@ -8022,7 +8172,12 @@ async function handleHistoryDelete(cwd, req, sendSse) {
|
|
|
8022
8172
|
const deleted = state.entries.find((entry) => entry.id === body.id);
|
|
8023
8173
|
const updated = deleteQueryHistoryEntry(state, body.id);
|
|
8024
8174
|
saveQueryHistory(cwd, updated);
|
|
8025
|
-
sendSse?.("db-query", JSON.stringify({
|
|
8175
|
+
sendSse?.("db-query", JSON.stringify({
|
|
8176
|
+
action: "delete",
|
|
8177
|
+
dbId: deleted?.dbId,
|
|
8178
|
+
schema: deleted?.schema,
|
|
8179
|
+
id: body.id
|
|
8180
|
+
}));
|
|
8026
8181
|
return json({ ok: true });
|
|
8027
8182
|
}
|
|
8028
8183
|
async function handleHistoryClear(cwd, req, sendSse) {
|
|
@@ -8035,9 +8190,12 @@ async function handleHistoryClear(cwd, req, sendSse) {
|
|
|
8035
8190
|
body = {};
|
|
8036
8191
|
}
|
|
8037
8192
|
const state = loadQueryHistory(cwd);
|
|
8038
|
-
const
|
|
8193
|
+
const schema = normalizeSchemaParam(body.schema);
|
|
8194
|
+
if (schema instanceof Response)
|
|
8195
|
+
return schema;
|
|
8196
|
+
const updated = clearQueryHistory(state, body.db, schema);
|
|
8039
8197
|
saveQueryHistory(cwd, updated);
|
|
8040
|
-
sendSse?.("db-query", JSON.stringify({ action: "clear", dbId: body.db }));
|
|
8198
|
+
sendSse?.("db-query", JSON.stringify({ action: "clear", dbId: body.db, schema }));
|
|
8041
8199
|
return json({ ok: true });
|
|
8042
8200
|
}
|
|
8043
8201
|
function formatCsvField(value) {
|
|
@@ -8055,7 +8213,7 @@ function formatCsvField(value) {
|
|
|
8055
8213
|
return str;
|
|
8056
8214
|
}
|
|
8057
8215
|
async function handleExport(cwd, url, omitDirNames) {
|
|
8058
|
-
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
|
|
8216
|
+
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
|
|
8059
8217
|
if (r instanceof Response)
|
|
8060
8218
|
return r;
|
|
8061
8219
|
const table = url.searchParams.get("table");
|
|
@@ -8156,7 +8314,7 @@ async function handleExport(cwd, url, omitDirNames) {
|
|
|
8156
8314
|
}
|
|
8157
8315
|
}
|
|
8158
8316
|
async function handleColumns(cwd, url, omitDirNames) {
|
|
8159
|
-
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
|
|
8317
|
+
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
|
|
8160
8318
|
if (r instanceof Response)
|
|
8161
8319
|
return r;
|
|
8162
8320
|
const table = url.searchParams.get("table");
|
|
@@ -8165,13 +8323,18 @@ async function handleColumns(cwd, url, omitDirNames) {
|
|
|
8165
8323
|
try {
|
|
8166
8324
|
const adapter = await getAdapter(r, cwd);
|
|
8167
8325
|
const columns = adapter.getColumns(table);
|
|
8168
|
-
return json({
|
|
8326
|
+
return json({
|
|
8327
|
+
dbId: r.dbId,
|
|
8328
|
+
...r.schema ? { schema: r.schema } : {},
|
|
8329
|
+
table,
|
|
8330
|
+
columns
|
|
8331
|
+
});
|
|
8169
8332
|
} catch (err) {
|
|
8170
8333
|
return handleError("database", "get columns", err);
|
|
8171
8334
|
}
|
|
8172
8335
|
}
|
|
8173
8336
|
async function handleDdl(cwd, url, omitDirNames) {
|
|
8174
|
-
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames);
|
|
8337
|
+
const r = resolveDb(cwd, url.searchParams.get("db"), omitDirNames, url.searchParams.get("schema"));
|
|
8175
8338
|
if (r instanceof Response)
|
|
8176
8339
|
return r;
|
|
8177
8340
|
const table = url.searchParams.get("table");
|
|
@@ -8181,7 +8344,13 @@ async function handleDdl(cwd, url, omitDirNames) {
|
|
|
8181
8344
|
const adapter = await getAdapter(r, cwd);
|
|
8182
8345
|
const sql = adapter.getCreateStatement(table);
|
|
8183
8346
|
const triggers = adapter.getTriggers(table);
|
|
8184
|
-
return json({
|
|
8347
|
+
return json({
|
|
8348
|
+
dbId: r.dbId,
|
|
8349
|
+
...r.schema ? { schema: r.schema } : {},
|
|
8350
|
+
table,
|
|
8351
|
+
sql,
|
|
8352
|
+
triggers
|
|
8353
|
+
});
|
|
8185
8354
|
} catch (err) {
|
|
8186
8355
|
return handleError("database", "get DDL", err);
|
|
8187
8356
|
}
|
|
@@ -8192,7 +8361,7 @@ async function handleSearchStart(cwd, req, omitDirNames) {
|
|
|
8192
8361
|
return body;
|
|
8193
8362
|
if (!body.db || !body.term)
|
|
8194
8363
|
return textError("missing db or term", 400);
|
|
8195
|
-
const r = resolveDb(cwd, body.db, omitDirNames);
|
|
8364
|
+
const r = resolveDb(cwd, body.db, omitDirNames, body.schema);
|
|
8196
8365
|
if (r instanceof Response)
|
|
8197
8366
|
return r;
|
|
8198
8367
|
const jobId = makeId("search");
|
|
@@ -8200,6 +8369,7 @@ async function handleSearchStart(cwd, req, omitDirNames) {
|
|
|
8200
8369
|
const job = {
|
|
8201
8370
|
id: jobId,
|
|
8202
8371
|
dbId: body.db,
|
|
8372
|
+
...r.schema ? { schema: r.schema } : {},
|
|
8203
8373
|
scannedTables: 0,
|
|
8204
8374
|
totalTables: 0,
|
|
8205
8375
|
hits: [],
|
|
@@ -8237,7 +8407,10 @@ async function handleSearchStart(cwd, req, omitDirNames) {
|
|
|
8237
8407
|
const pkCols = getPrimaryKeyColumns(adapter, table);
|
|
8238
8408
|
const columns = adapter.getColumns(table);
|
|
8239
8409
|
const hits = searchTable(adapter, table, columns, term, maxHitsPerTable, includeNonText, pkCols);
|
|
8240
|
-
job.hits.push(...hits)
|
|
8410
|
+
job.hits.push(...hits.map((hit) => ({
|
|
8411
|
+
...r.schema ? { schema: r.schema } : {},
|
|
8412
|
+
...hit
|
|
8413
|
+
})));
|
|
8241
8414
|
job.scannedTables++;
|
|
8242
8415
|
}
|
|
8243
8416
|
job.done = true;
|
|
@@ -8261,6 +8434,7 @@ function handleSearchStatus(url) {
|
|
|
8261
8434
|
const result = {
|
|
8262
8435
|
jobId: job.id,
|
|
8263
8436
|
dbId: job.dbId,
|
|
8437
|
+
schema: job.schema,
|
|
8264
8438
|
scannedTables: job.scannedTables,
|
|
8265
8439
|
totalTables: job.totalTables,
|
|
8266
8440
|
currentTable: job.currentTable,
|
|
@@ -8292,7 +8466,10 @@ async function openRegisteredDockerSnapshotSource(info, requestedContainers) {
|
|
|
8292
8466
|
}
|
|
8293
8467
|
async function handleSnapshotList(cwd, url) {
|
|
8294
8468
|
const dbId = url.searchParams.get("db") || undefined;
|
|
8295
|
-
const
|
|
8469
|
+
const schema = normalizeSchemaParam(url.searchParams.get("schema"));
|
|
8470
|
+
if (schema instanceof Response)
|
|
8471
|
+
return schema;
|
|
8472
|
+
const snapshots = await listSnapshots(cwd, dbId, schema);
|
|
8296
8473
|
return json({ snapshots });
|
|
8297
8474
|
}
|
|
8298
8475
|
function sanitizeSnapshotTables(tables) {
|
|
@@ -8343,10 +8520,11 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
|
|
|
8343
8520
|
}
|
|
8344
8521
|
}
|
|
8345
8522
|
if (!source) {
|
|
8346
|
-
const r = resolveDb(cwd, body.db, omitDirNames);
|
|
8523
|
+
const r = resolveDb(cwd, body.db, omitDirNames, body.schema);
|
|
8347
8524
|
if (r instanceof Response)
|
|
8348
8525
|
return r;
|
|
8349
8526
|
source = await getAdapter(r, cwd);
|
|
8527
|
+
body.schema = r.schema;
|
|
8350
8528
|
}
|
|
8351
8529
|
if (!containers || containers.length === 0) {
|
|
8352
8530
|
const sqlAdapter = source;
|
|
@@ -8373,11 +8551,13 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
|
|
|
8373
8551
|
sendSse?.("db-snapshot", JSON.stringify({
|
|
8374
8552
|
action: "progress",
|
|
8375
8553
|
dbId: snapshotDbId,
|
|
8554
|
+
schema: body.schema,
|
|
8376
8555
|
table,
|
|
8377
8556
|
done
|
|
8378
8557
|
}));
|
|
8379
8558
|
}, {
|
|
8380
8559
|
signal: abortController.signal,
|
|
8560
|
+
schema: body.schema,
|
|
8381
8561
|
onSnapshotId: (id) => {
|
|
8382
8562
|
activeSnapshotId = id;
|
|
8383
8563
|
snapshotJob.snapshotId = id;
|
|
@@ -8385,6 +8565,7 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
|
|
|
8385
8565
|
sendSse?.("db-snapshot", JSON.stringify({
|
|
8386
8566
|
action: "started",
|
|
8387
8567
|
dbId: snapshotDbId,
|
|
8568
|
+
schema: body.schema,
|
|
8388
8569
|
id
|
|
8389
8570
|
}));
|
|
8390
8571
|
}
|
|
@@ -8392,6 +8573,7 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
|
|
|
8392
8573
|
sendSse?.("db-snapshot", JSON.stringify({
|
|
8393
8574
|
action: "created",
|
|
8394
8575
|
dbId: snapshotDbId,
|
|
8576
|
+
schema: body.schema,
|
|
8395
8577
|
id: snapshotId
|
|
8396
8578
|
}));
|
|
8397
8579
|
} catch (err) {
|
|
@@ -8399,6 +8581,7 @@ async function handleSnapshotCreate(cwd, req, sendSse, omitDirNames) {
|
|
|
8399
8581
|
sendSse?.("db-snapshot", JSON.stringify({
|
|
8400
8582
|
action: "error",
|
|
8401
8583
|
dbId: snapshotDbId,
|
|
8584
|
+
schema: body.schema,
|
|
8402
8585
|
error: err instanceof Error ? err.message : String(err)
|
|
8403
8586
|
}));
|
|
8404
8587
|
} finally {
|
|
@@ -8523,6 +8706,7 @@ async function handleClose(cwd, req, omitDirNames) {
|
|
|
8523
8706
|
const info = findDockerServiceByDbId(cwd, body.db, undefined, omitDirNames);
|
|
8524
8707
|
if (!info) {
|
|
8525
8708
|
dockerAdapterCache.close(body.db);
|
|
8709
|
+
dockerAdapterCache.closePrefix(`${body.db}\x00`);
|
|
8526
8710
|
closeRedisAdapter(body.db);
|
|
8527
8711
|
closeElasticsearchAdapter(body.db);
|
|
8528
8712
|
return json({ ok: true });
|
|
@@ -8535,6 +8719,7 @@ async function handleClose(cwd, req, omitDirNames) {
|
|
|
8535
8719
|
return r;
|
|
8536
8720
|
if (r.docker) {
|
|
8537
8721
|
dockerAdapterCache.close(r.dbId);
|
|
8722
|
+
dockerAdapterCache.closePrefix(`${r.dbId}\x00`);
|
|
8538
8723
|
} else {
|
|
8539
8724
|
closeConnection(r.resolved);
|
|
8540
8725
|
}
|
|
@@ -8567,6 +8752,10 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
8567
8752
|
methods: ["GET"],
|
|
8568
8753
|
handler: () => handleFiles(cwd, omitDirNames)
|
|
8569
8754
|
},
|
|
8755
|
+
"/_db/schemas": {
|
|
8756
|
+
methods: ["GET"],
|
|
8757
|
+
handler: () => handleSchemas(cwd, url, omitDirNames)
|
|
8758
|
+
},
|
|
8570
8759
|
"/_db/schema": {
|
|
8571
8760
|
methods: ["GET"],
|
|
8572
8761
|
handler: () => handleSchema(cwd, url, omitDirNames)
|
|
@@ -8664,7 +8853,7 @@ async function handleDatabaseRoute(req, url, cwd, omitDirNames, sideEffectAllowe
|
|
|
8664
8853
|
}
|
|
8665
8854
|
}, sideEffectAllowed, wrapResponse, (err) => handleError("database", "handle database request", err));
|
|
8666
8855
|
}
|
|
8667
|
-
var initialized = false, dockerAdapterCache, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
|
|
8856
|
+
var initialized = false, dockerAdapterCache, MAX_SCHEMA_NAME_LEN = 1024, EXPORT_MAX_ROWS = 1e5, MAX_TABS_BODY_BYTES = 1e6, MAX_SNAPSHOT_TABLES = 512, MAX_SNAPSHOT_TABLE_NAME_LEN = 1024, searchJobs, snapshotJobs, DOCKER_CLOSE_REGISTRY, SNAPSHOT_DOCKER_SOURCE_REGISTRY;
|
|
8668
8857
|
var init_handle = __esm(() => {
|
|
8669
8858
|
init_docker();
|
|
8670
8859
|
init_docker_utils();
|
|
@@ -8684,8 +8873,14 @@ var init_handle = __esm(() => {
|
|
|
8684
8873
|
searchJobs = new Map;
|
|
8685
8874
|
snapshotJobs = new Map;
|
|
8686
8875
|
DOCKER_CLOSE_REGISTRY = {
|
|
8687
|
-
postgresql: (dbId) =>
|
|
8688
|
-
|
|
8876
|
+
postgresql: (dbId) => {
|
|
8877
|
+
dockerAdapterCache.close(dbId);
|
|
8878
|
+
dockerAdapterCache.closePrefix(`${dbId}\x00`);
|
|
8879
|
+
},
|
|
8880
|
+
mysql: (dbId) => {
|
|
8881
|
+
dockerAdapterCache.close(dbId);
|
|
8882
|
+
dockerAdapterCache.closePrefix(`${dbId}\x00`);
|
|
8883
|
+
},
|
|
8689
8884
|
redis: closeRedisAdapter,
|
|
8690
8885
|
elasticsearch: closeElasticsearchAdapter
|
|
8691
8886
|
};
|