@carllee1983/dbcli 9.0.0 → 9.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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/skills/dbcli/reference.md +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/skills/dbcli/reference.md +1 -1
- package/CHANGELOG.md +147 -14
- package/README.dev.md +1 -1
- package/README.md +77 -0
- package/assets/reference.md +1 -1
- package/dist/cli-runtime.mjs +818 -175
- package/dist/cli.mjs +3 -2
- package/dist/core.d.ts +452 -6
- package/dist/core.mjs +223 -22
- package/gemini-extension.json +1 -1
- package/package.json +3 -2
- package/plugins/dbcli-agent/.codex-plugin/plugin.json +1 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +1 -1
- package/skills/dbcli/reference.md +1 -1
package/dist/cli-runtime.mjs
CHANGED
|
@@ -2087,7 +2087,7 @@ var package_default;
|
|
|
2087
2087
|
var init_package = __esm(() => {
|
|
2088
2088
|
package_default = {
|
|
2089
2089
|
name: "@carllee1983/dbcli",
|
|
2090
|
-
version: "9.
|
|
2090
|
+
version: "9.1.1",
|
|
2091
2091
|
description: "Database CLI for AI agents",
|
|
2092
2092
|
type: "module",
|
|
2093
2093
|
publishConfig: {
|
|
@@ -2173,9 +2173,10 @@ var init_package = __esm(() => {
|
|
|
2173
2173
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
2174
2174
|
"plan:check": "bun run scripts/check-plan-acceptance.ts",
|
|
2175
2175
|
"forgeflow:check": "bun run scripts/check-forgeflow-adoption.ts && bun run scripts/check-forgeflow-handoff.ts",
|
|
2176
|
+
"forgeflow:contract": "bun run scripts/check-forgeflow-contract.ts",
|
|
2176
2177
|
typecheck: "tsc --noEmit --pretty false",
|
|
2177
2178
|
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
2178
|
-
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
2179
|
+
"test:perf": "bun test --timeout 30000 ./tests/perf/*.bench.ts",
|
|
2179
2180
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
2180
2181
|
"format:check": "prettier --check .",
|
|
2181
2182
|
format: "prettier --write .",
|
|
@@ -6406,13 +6407,27 @@ function parseStatementTimeoutOption(value) {
|
|
|
6406
6407
|
}
|
|
6407
6408
|
return parsed;
|
|
6408
6409
|
}
|
|
6410
|
+
function isSqliteMemoryTarget(value) {
|
|
6411
|
+
const trimmed = value.trim();
|
|
6412
|
+
if (trimmed.toLowerCase() === ":memory:")
|
|
6413
|
+
return true;
|
|
6414
|
+
if (!trimmed.toLowerCase().startsWith("file:"))
|
|
6415
|
+
return false;
|
|
6416
|
+
const withoutScheme = trimmed.slice("file:".length);
|
|
6417
|
+
const queryStart = withoutScheme.indexOf("?");
|
|
6418
|
+
const path = queryStart === -1 ? withoutScheme : withoutScheme.slice(0, queryStart);
|
|
6419
|
+
if (path.toLowerCase() === ":memory:")
|
|
6420
|
+
return true;
|
|
6421
|
+
const query = queryStart === -1 ? "" : withoutScheme.slice(queryStart + 1);
|
|
6422
|
+
return new URLSearchParams(query).get("mode")?.toLowerCase() === "memory";
|
|
6423
|
+
}
|
|
6409
6424
|
function validateFormat(value, allowedFormats, commandName) {
|
|
6410
6425
|
if (!allowedFormats.includes(value)) {
|
|
6411
6426
|
const allowed = allowedFormats.join(", ");
|
|
6412
6427
|
throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
|
|
6413
6428
|
}
|
|
6414
6429
|
}
|
|
6415
|
-
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
6430
|
+
var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, OptStringOrEnvRef, OptNumberOrEnvRef, MIN_CONNECTION_TIMEOUT_MS = 100, MAX_CONNECTION_TIMEOUT_MS = 600000, MIN_STATEMENT_TIMEOUT_MS = 0, MAX_STATEMENT_TIMEOUT_MS = 3600000, TimeoutField, MongoDBConnectionConfigSchema, SqlConnectionConfigSchema, SQLITE_MEMORY_TARGET_MESSAGE, SqliteConnectionConfigSchema, RedisConnectionConfigSchema, ElasticsearchConnectionConfigSchema, ConnectionConfigSchema, PermissionSchema, EnvironmentLabelSchema, MetadataSchema, BLACKLIST_KEY_ALIASES, BlacklistConfigSchema, RedisMaskRuleSchema, RedisConfigSchema, DEFAULT_AUDIT_ROTATION, AuditRotationConfigSchema, AuditConfigSchema, DbcliConfigSchema, SqlNamedConnectionSchema, SqliteNamedConnectionSchema, MongoDBNamedConnectionSchema, RedisNamedConnectionSchema, ElasticsearchNamedConnectionSchema, NamedConnectionUnion, NamedConnectionSchema, DbcliConfigV2Schema;
|
|
6416
6431
|
var init_validation = __esm(() => {
|
|
6417
6432
|
init_zod();
|
|
6418
6433
|
EnvRefSchema = exports_external.object({
|
|
@@ -6449,6 +6464,21 @@ var init_validation = __esm(() => {
|
|
|
6449
6464
|
database: StringOrEnvRef,
|
|
6450
6465
|
...TimeoutField
|
|
6451
6466
|
});
|
|
6467
|
+
SQLITE_MEMORY_TARGET_MESSAGE = "SQLite in-memory databases are refused: a memory target is a different database on every " + "connection, so it cannot be the connection identity the blacklist, audit log and schema " + "cache are scoped to. Point `file` at a database file on disk. See ADR-0038.";
|
|
6468
|
+
SqliteConnectionConfigSchema = exports_external.object({
|
|
6469
|
+
system: exports_external.literal("sqlite"),
|
|
6470
|
+
file: exports_external.union([exports_external.string().min(1), EnvRefSchema]).superRefine((value, ctx) => {
|
|
6471
|
+
if (typeof value === "string" && isSqliteMemoryTarget(value)) {
|
|
6472
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, message: SQLITE_MEMORY_TARGET_MESSAGE });
|
|
6473
|
+
}
|
|
6474
|
+
}),
|
|
6475
|
+
host: OptStringOrEnvRef,
|
|
6476
|
+
port: exports_external.union([exports_external.number().int(), EnvRefSchema]).optional().default(0),
|
|
6477
|
+
user: OptStringOrEnvRef,
|
|
6478
|
+
password: exports_external.union([exports_external.string(), EnvRefSchema]).optional().default(""),
|
|
6479
|
+
database: OptStringOrEnvRef,
|
|
6480
|
+
...TimeoutField
|
|
6481
|
+
});
|
|
6452
6482
|
RedisConnectionConfigSchema = exports_external.object({
|
|
6453
6483
|
system: exports_external.literal("redis"),
|
|
6454
6484
|
host: StringOrEnvRef,
|
|
@@ -6475,6 +6505,7 @@ var init_validation = __esm(() => {
|
|
|
6475
6505
|
});
|
|
6476
6506
|
ConnectionConfigSchema = exports_external.union([
|
|
6477
6507
|
SqlConnectionConfigSchema,
|
|
6508
|
+
SqliteConnectionConfigSchema,
|
|
6478
6509
|
MongoDBConnectionConfigSchema,
|
|
6479
6510
|
RedisConnectionConfigSchema,
|
|
6480
6511
|
ElasticsearchConnectionConfigSchema
|
|
@@ -6545,6 +6576,11 @@ var init_validation = __esm(() => {
|
|
|
6545
6576
|
envFile: exports_external.string().optional(),
|
|
6546
6577
|
environment: EnvironmentLabelSchema
|
|
6547
6578
|
});
|
|
6579
|
+
SqliteNamedConnectionSchema = SqliteConnectionConfigSchema.extend({
|
|
6580
|
+
permission: PermissionSchema,
|
|
6581
|
+
envFile: exports_external.string().optional(),
|
|
6582
|
+
environment: EnvironmentLabelSchema
|
|
6583
|
+
});
|
|
6548
6584
|
MongoDBNamedConnectionSchema = MongoDBConnectionConfigSchema.extend({
|
|
6549
6585
|
permission: PermissionSchema,
|
|
6550
6586
|
envFile: exports_external.string().optional(),
|
|
@@ -6562,6 +6598,7 @@ var init_validation = __esm(() => {
|
|
|
6562
6598
|
});
|
|
6563
6599
|
NamedConnectionUnion = exports_external.union([
|
|
6564
6600
|
SqlNamedConnectionSchema,
|
|
6601
|
+
SqliteNamedConnectionSchema,
|
|
6565
6602
|
MongoDBNamedConnectionSchema,
|
|
6566
6603
|
RedisNamedConnectionSchema,
|
|
6567
6604
|
ElasticsearchNamedConnectionSchema
|
|
@@ -8815,7 +8852,8 @@ var init_messages = __esm(() => {
|
|
|
8815
8852
|
connection_not_found: "Connection '{name}' does not exist",
|
|
8816
8853
|
cannot_remove_last: "Cannot remove the last connection",
|
|
8817
8854
|
connection_already_exists: "Connection '{name}' already exists",
|
|
8818
|
-
rename_invalid_format: "Usage: --rename <old-name>:<new-name>"
|
|
8855
|
+
rename_invalid_format: "Usage: --rename <old-name>:<new-name>",
|
|
8856
|
+
prompt_sqlite_file: "SQLite database file path: "
|
|
8819
8857
|
},
|
|
8820
8858
|
inspect: {
|
|
8821
8859
|
description: "Read-only snapshot of connection, permission, blacklist, objects, snippets, and suggested next commands.",
|
|
@@ -8959,7 +8997,7 @@ var init_messages = __esm(() => {
|
|
|
8959
8997
|
elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
8960
8998
|
escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
|
|
8961
8999
|
multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
|
|
8962
|
-
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires
|
|
9000
|
+
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires admin for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
|
|
8963
9001
|
permission_denied_reason: "Permission denied: {reason}",
|
|
8964
9002
|
table_not_found: "Table not found: {table}",
|
|
8965
9003
|
unsupported_lang: "Language '{lang}' not supported, using English",
|
|
@@ -8984,7 +9022,8 @@ Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactiv
|
|
|
8984
9022
|
env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
|
|
8985
9023
|
Set {envKey} in .env or environment variables.
|
|
8986
9024
|
Hint: run 'export {envKey}=<value>' and retry`,
|
|
8987
|
-
invalid_output_format: "--format {format} is not supported here \u2014 use text or json"
|
|
9025
|
+
invalid_output_format: "--format {format} is not supported here \u2014 use text or json",
|
|
9026
|
+
sqlite_file_required: "A SQLite connection needs a file path. Pass --file <path> or run init interactively."
|
|
8988
9027
|
},
|
|
8989
9028
|
success: {
|
|
8990
9029
|
inserted: "Successfully inserted {count} row(s)",
|
|
@@ -9149,7 +9188,8 @@ var init_messages2 = __esm(() => {
|
|
|
9149
9188
|
connection_not_found: "\u9023\u7DDA '{name}' \u4E0D\u5B58\u5728",
|
|
9150
9189
|
cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
|
|
9151
9190
|
connection_already_exists: "\u9023\u7DDA '{name}' \u5DF2\u5B58\u5728",
|
|
9152
|
-
rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>"
|
|
9191
|
+
rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>",
|
|
9192
|
+
prompt_sqlite_file: "SQLite \u8CC7\u6599\u5EAB\u6A94\u6848\u8DEF\u5F91\uFF1A"
|
|
9153
9193
|
},
|
|
9154
9194
|
inspect: {
|
|
9155
9195
|
description: "\u552F\u8B80\u6AA2\u8996\uFF1A\u9023\u7DDA\u3001\u6B0A\u9650\u3001\u9ED1\u540D\u55AE\u3001\u7269\u4EF6\u3001\u7247\u6BB5\u8207\u5EFA\u8B70\u4E0B\u4E00\u6B65\u6307\u4EE4\u3002",
|
|
@@ -9293,7 +9333,7 @@ var init_messages2 = __esm(() => {
|
|
|
9293
9333
|
elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
9294
9334
|
escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
|
|
9295
9335
|
multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
|
|
9296
|
-
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\
|
|
9336
|
+
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u9700\u8981 admin \u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
|
|
9297
9337
|
permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
|
|
9298
9338
|
table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
|
|
9299
9339
|
unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
|
|
@@ -9318,7 +9358,8 @@ var init_messages2 = __esm(() => {
|
|
|
9318
9358
|
env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
|
|
9319
9359
|
\u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
|
|
9320
9360
|
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
|
|
9321
|
-
invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json"
|
|
9361
|
+
invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json",
|
|
9362
|
+
sqlite_file_required: "SQLite \u9023\u7DDA\u9700\u8981\u4E00\u500B\u6A94\u6848\u8DEF\u5F91\u3002\u8ACB\u7528 --file <path>\uFF0C\u6216\u4EE5\u4E92\u52D5\u6A21\u5F0F\u57F7\u884C init\u3002"
|
|
9322
9363
|
},
|
|
9323
9364
|
success: {
|
|
9324
9365
|
inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
|
|
@@ -10246,16 +10287,23 @@ var init_redaction = __esm(() => {
|
|
|
10246
10287
|
});
|
|
10247
10288
|
|
|
10248
10289
|
// src/adapters/types.ts
|
|
10249
|
-
var DATABASE_SYSTEMS, ConnectionError;
|
|
10290
|
+
var DATABASE_SYSTEMS, SQL_DATABASE_SYSTEMS, ConnectionError;
|
|
10250
10291
|
var init_types2 = __esm(() => {
|
|
10251
10292
|
DATABASE_SYSTEMS = Object.freeze([
|
|
10252
10293
|
"postgresql",
|
|
10253
10294
|
"mysql",
|
|
10254
10295
|
"mariadb",
|
|
10296
|
+
"sqlite",
|
|
10255
10297
|
"mongodb",
|
|
10256
10298
|
"redis",
|
|
10257
10299
|
"elasticsearch"
|
|
10258
10300
|
]);
|
|
10301
|
+
SQL_DATABASE_SYSTEMS = Object.freeze([
|
|
10302
|
+
"postgresql",
|
|
10303
|
+
"mysql",
|
|
10304
|
+
"mariadb",
|
|
10305
|
+
"sqlite"
|
|
10306
|
+
]);
|
|
10259
10307
|
ConnectionError = class ConnectionError extends Error {
|
|
10260
10308
|
code;
|
|
10261
10309
|
hints;
|
|
@@ -10416,6 +10464,38 @@ var init_capabilities = __esm(() => {
|
|
|
10416
10464
|
...SQL_BASE,
|
|
10417
10465
|
check: cap("limited", "readonly", "SQL-only and strongest on MySQL/MariaDB.")
|
|
10418
10466
|
}),
|
|
10467
|
+
sqlite: Object.freeze({
|
|
10468
|
+
...SQL_BASE,
|
|
10469
|
+
init: cap("supported", "local-write", "Asks for a file path and a permission; the file must already exist."),
|
|
10470
|
+
use: cap("supported", "local-write", "Switches connections and displays the file path."),
|
|
10471
|
+
schemaFullScan: cap("unsupported", "none", "Full schema cache scan is not implemented for SQLite."),
|
|
10472
|
+
lint: cap("unsupported", "none", "Static lint is not enabled for the SQLite dialect yet."),
|
|
10473
|
+
queries: cap("supported", "local-write", "Snippets may declare `engine: sqlite` and bind with `?` placeholders."),
|
|
10474
|
+
insert: cap("supported", "db-write", "Row insert through the shared data-executor path."),
|
|
10475
|
+
update: cap("supported", "db-write", "Row update with a mandatory --where clause."),
|
|
10476
|
+
delete: cap("supported", "db-write", "Row delete with a mandatory --where clause."),
|
|
10477
|
+
export: cap("supported", "local-write", "Exports SELECT results to json, csv or html through the shared query path."),
|
|
10478
|
+
check: cap("unsupported", "none", "Data health check is not enabled for SQLite."),
|
|
10479
|
+
diff: cap("unsupported", "none", "Schema snapshots are not enabled for SQLite."),
|
|
10480
|
+
migrate: cap("unsupported", "none", "SQLite ALTER TABLE covers few operations and a column change is a table rebuild; no DDL generator exists."),
|
|
10481
|
+
shell: cap("unsupported", "none", "The REPL is not enabled for SQLite."),
|
|
10482
|
+
doctor: cap("supported", "readonly", "Checks the file exists, is readable, and is writable when the permission allows writes."),
|
|
10483
|
+
inspect: cap("unsupported", "none", "Inspection collectors are not enabled for SQLite."),
|
|
10484
|
+
report: cap("unsupported", "none", "Report diagnostics query engine-specific statistics views."),
|
|
10485
|
+
guide: cap("unsupported", "none", "Guides depend on engine-specific EXPLAIN output."),
|
|
10486
|
+
explain: cap("unsupported", "none", "EXPLAIN handling is not enabled for SQLite."),
|
|
10487
|
+
plan: cap("unsupported", "none", "Plan capture is not enabled for SQLite."),
|
|
10488
|
+
impactAssess: cap("unsupported", "none", "Impact assessment is not enabled for SQLite."),
|
|
10489
|
+
assert: cap("unsupported", "none", "Assertions are not enabled for SQLite."),
|
|
10490
|
+
snapshot: cap("unsupported", "none", "Result snapshots are not enabled for SQLite."),
|
|
10491
|
+
verify: cap("unsupported", "none", "Write verification is not enabled for SQLite."),
|
|
10492
|
+
semantic: cap("unsupported", "none", "Semantic context work is not enabled for SQLite."),
|
|
10493
|
+
design: cap("unsupported", "none", "Schema design work is not enabled for SQLite."),
|
|
10494
|
+
proxy: cap("not-applicable", "none", "SQLite has no network protocol to proxy."),
|
|
10495
|
+
proxyAnalyze: cap("not-applicable", "none", "There are no SQLite proxy events to analyse."),
|
|
10496
|
+
password: cap("not-applicable", "none", "A SQLite connection has no credential to store or rotate."),
|
|
10497
|
+
queryLimitGuard: cap("limited", "readonly", "Applies the same LIMIT rewrite as the other SQL engines; under query-only the handle is opened read-only, which a database with an unreplayed WAL cannot satisfy.")
|
|
10498
|
+
}),
|
|
10419
10499
|
mysql: Object.freeze(SQL_BASE),
|
|
10420
10500
|
mariadb: Object.freeze(SQL_BASE),
|
|
10421
10501
|
mongodb: Object.freeze({
|
|
@@ -10915,7 +10995,7 @@ var init_encoding = __esm(() => {
|
|
|
10915
10995
|
|
|
10916
10996
|
// src/adapters/identifier-quote.ts
|
|
10917
10997
|
function quoteCharFor(dialect) {
|
|
10918
|
-
return dialect === "postgresql" ? '"' : "`";
|
|
10998
|
+
return dialect === "postgresql" || dialect === "sqlite" ? '"' : "`";
|
|
10919
10999
|
}
|
|
10920
11000
|
function assertQuotable(name, what) {
|
|
10921
11001
|
if (name.length === 0) {
|
|
@@ -11624,6 +11704,208 @@ var init_mysql_adapter = __esm(() => {
|
|
|
11624
11704
|
init_connection_error_message();
|
|
11625
11705
|
});
|
|
11626
11706
|
|
|
11707
|
+
// src/adapters/sqlite-adapter.ts
|
|
11708
|
+
import { Database } from "bun:sqlite";
|
|
11709
|
+
function mapSqliteError(error, file, readonly) {
|
|
11710
|
+
if (error instanceof ConnectionError)
|
|
11711
|
+
return error;
|
|
11712
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11713
|
+
const code = String(error?.code ?? "");
|
|
11714
|
+
if (readonly && (code.startsWith("SQLITE_CANTOPEN") || /unable to open database/i.test(message))) {
|
|
11715
|
+
return new ConnectionError("CONNECTION_REJECTED", `Cannot open '${file}' read-only: the database has an unreplayed write-ahead log, ` + `and replaying it is a write. A query-only connection cannot recover it.`, [
|
|
11716
|
+
"Let the owning application close the database cleanly, which checkpoints the WAL.",
|
|
11717
|
+
"Or connect with a permission above query-only, which opens the file read-write.",
|
|
11718
|
+
`Original driver message: ${message}`
|
|
11719
|
+
]);
|
|
11720
|
+
}
|
|
11721
|
+
if (/readonly database|attempt to write a readonly/i.test(message)) {
|
|
11722
|
+
return new ConnectionError("CONNECTION_REJECTED", `The SQLite connection is open read-only: ${message}`, [
|
|
11723
|
+
"query-only opens the database file read-only, so no write can reach it.",
|
|
11724
|
+
"Raise the connection permission if the write is intended."
|
|
11725
|
+
]);
|
|
11726
|
+
}
|
|
11727
|
+
if (/no such table/i.test(message)) {
|
|
11728
|
+
return new ConnectionError("TABLE_NOT_FOUND", message, [
|
|
11729
|
+
"Run `dbcli list` to see the tables this database holds."
|
|
11730
|
+
]);
|
|
11731
|
+
}
|
|
11732
|
+
if (/no such column/i.test(message)) {
|
|
11733
|
+
return new ConnectionError("COLUMN_NOT_FOUND", message, [
|
|
11734
|
+
"Run `dbcli schema <table>` to see the columns this table holds."
|
|
11735
|
+
]);
|
|
11736
|
+
}
|
|
11737
|
+
if (/syntax error/i.test(message)) {
|
|
11738
|
+
return new ConnectionError("SQL_SYNTAX_ERROR", message, [
|
|
11739
|
+
"Check the statement against SQLite syntax; it differs from PostgreSQL and MySQL."
|
|
11740
|
+
]);
|
|
11741
|
+
}
|
|
11742
|
+
return new ConnectionError("UNKNOWN", message, [`SQLite database: ${file}`]);
|
|
11743
|
+
}
|
|
11744
|
+
|
|
11745
|
+
class SQLiteAdapter {
|
|
11746
|
+
db = null;
|
|
11747
|
+
openedReadonly = true;
|
|
11748
|
+
connected = false;
|
|
11749
|
+
file;
|
|
11750
|
+
constructor(options) {
|
|
11751
|
+
if (!options.file) {
|
|
11752
|
+
throw new ConnectionError("UNKNOWN", "SQLite connection is missing its `file` path", [
|
|
11753
|
+
"Set `file` in the connection configuration to the database file path."
|
|
11754
|
+
]);
|
|
11755
|
+
}
|
|
11756
|
+
this.file = options.file;
|
|
11757
|
+
}
|
|
11758
|
+
async connect() {
|
|
11759
|
+
const target = Bun.file(this.file);
|
|
11760
|
+
if (!await target.exists()) {
|
|
11761
|
+
throw new ConnectionError("ENOTFOUND", `SQLite database file not found: ${this.file}`, [
|
|
11762
|
+
"dbcli never creates a database file; point `file` at one that already exists.",
|
|
11763
|
+
"Check the path in the connection configuration."
|
|
11764
|
+
]);
|
|
11765
|
+
}
|
|
11766
|
+
let header;
|
|
11767
|
+
try {
|
|
11768
|
+
header = await target.slice(0, SQLITE_HEADER.length).text();
|
|
11769
|
+
} catch (error) {
|
|
11770
|
+
throw new ConnectionError("CONNECTION_REJECTED", `SQLite database file is not readable: ${this.file}`, [
|
|
11771
|
+
"Check the file permissions for the user running dbcli.",
|
|
11772
|
+
`Original error: ${error instanceof Error ? error.message : String(error)}`
|
|
11773
|
+
]);
|
|
11774
|
+
}
|
|
11775
|
+
if (target.size > 0 && header !== SQLITE_HEADER) {
|
|
11776
|
+
throw new ConnectionError("CONNECTION_REJECTED", `Not a SQLite database: ${this.file} does not start with the SQLite file header`, ["Point `file` at a SQLite database file."]);
|
|
11777
|
+
}
|
|
11778
|
+
this.connected = true;
|
|
11779
|
+
}
|
|
11780
|
+
async disconnect() {
|
|
11781
|
+
try {
|
|
11782
|
+
this.db?.close();
|
|
11783
|
+
} catch {}
|
|
11784
|
+
this.db = null;
|
|
11785
|
+
this.connected = false;
|
|
11786
|
+
}
|
|
11787
|
+
handle(readonly) {
|
|
11788
|
+
if (!this.connected)
|
|
11789
|
+
requireConnected(null);
|
|
11790
|
+
if (this.db && this.openedReadonly === readonly)
|
|
11791
|
+
return this.db;
|
|
11792
|
+
try {
|
|
11793
|
+
this.db?.close();
|
|
11794
|
+
} catch {}
|
|
11795
|
+
this.db = null;
|
|
11796
|
+
try {
|
|
11797
|
+
this.db = readonly ? new Database(this.file, { readonly: true }) : new Database(this.file, { readwrite: true, create: false });
|
|
11798
|
+
} catch (error) {
|
|
11799
|
+
throw mapSqliteError(error, this.file, readonly);
|
|
11800
|
+
}
|
|
11801
|
+
this.openedReadonly = readonly;
|
|
11802
|
+
return this.db;
|
|
11803
|
+
}
|
|
11804
|
+
async execute(sql, params, options) {
|
|
11805
|
+
const readonly = options?.sqlMode === "native-read-only";
|
|
11806
|
+
const db = this.handle(readonly);
|
|
11807
|
+
try {
|
|
11808
|
+
const statement = db.prepare(sql);
|
|
11809
|
+
try {
|
|
11810
|
+
const columnNames = statement.columnNames;
|
|
11811
|
+
if (columnNames.length === 0) {
|
|
11812
|
+
const changes = statement.run(...params ?? []);
|
|
11813
|
+
return {
|
|
11814
|
+
rows: [],
|
|
11815
|
+
affectedRows: changes.changes,
|
|
11816
|
+
lastInsertId: Number(changes.lastInsertRowid)
|
|
11817
|
+
};
|
|
11818
|
+
}
|
|
11819
|
+
const rows = statement.all(...params ?? []);
|
|
11820
|
+
return {
|
|
11821
|
+
rows,
|
|
11822
|
+
affectedRows: rows.length,
|
|
11823
|
+
rowCount: rows.length,
|
|
11824
|
+
columnNames: [...columnNames]
|
|
11825
|
+
};
|
|
11826
|
+
} finally {
|
|
11827
|
+
statement.finalize();
|
|
11828
|
+
}
|
|
11829
|
+
} catch (error) {
|
|
11830
|
+
throw mapSqliteError(error, this.file, readonly);
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11833
|
+
async testConnection() {
|
|
11834
|
+
const result = await this.execute("SELECT 1 as ok", [], {
|
|
11835
|
+
sqlMode: "native-read-only"
|
|
11836
|
+
});
|
|
11837
|
+
return result.rows.length > 0;
|
|
11838
|
+
}
|
|
11839
|
+
async getServerVersion() {
|
|
11840
|
+
const result = await this.execute("SELECT sqlite_version() as version", [], {
|
|
11841
|
+
sqlMode: "native-read-only"
|
|
11842
|
+
});
|
|
11843
|
+
return result.rows[0]?.version ?? "unknown";
|
|
11844
|
+
}
|
|
11845
|
+
async listTables() {
|
|
11846
|
+
const result = await this.execute(`SELECT m.name AS name,
|
|
11847
|
+
m.type AS type,
|
|
11848
|
+
(SELECT COUNT(*) FROM pragma_table_info(m.name)) AS column_count
|
|
11849
|
+
FROM sqlite_master m
|
|
11850
|
+
WHERE m.type IN ('table', 'view')
|
|
11851
|
+
AND m.name NOT LIKE 'sqlite\\_%' ESCAPE '\\'
|
|
11852
|
+
ORDER BY m.name`, [], { sqlMode: "native-read-only" });
|
|
11853
|
+
return result.rows.map((row) => ({
|
|
11854
|
+
name: row.name,
|
|
11855
|
+
columns: [],
|
|
11856
|
+
columnCount: row.column_count,
|
|
11857
|
+
rowCount: 0,
|
|
11858
|
+
estimatedRowCount: 0,
|
|
11859
|
+
tableType: row.type === "view" ? "view" : "table"
|
|
11860
|
+
}));
|
|
11861
|
+
}
|
|
11862
|
+
async getTableSchema(tableName, _options) {
|
|
11863
|
+
const info = await this.execute('SELECT cid, name, type, "notnull", dflt_value, pk FROM pragma_table_info(?)', [tableName], { sqlMode: "native-read-only" });
|
|
11864
|
+
if (info.rows.length === 0) {
|
|
11865
|
+
throw new ConnectionError("TABLE_NOT_FOUND", `Table not found: ${tableName}`, [
|
|
11866
|
+
"Run `dbcli list` to see the tables this database holds."
|
|
11867
|
+
]);
|
|
11868
|
+
}
|
|
11869
|
+
const foreignKeys = await this.execute('SELECT "from", "table", "to" FROM pragma_foreign_key_list(?)', [tableName], { sqlMode: "native-read-only" });
|
|
11870
|
+
const fkByColumn = new Map(foreignKeys.rows.map((fk) => [fk.from, { table: fk.table, column: fk.to ?? "rowid" }]));
|
|
11871
|
+
const ddl = await this.execute("SELECT sql FROM sqlite_master WHERE name = ? AND type = ?", [tableName, "table"], { sqlMode: "native-read-only" });
|
|
11872
|
+
const createSql = ddl.rows[0]?.sql ?? "";
|
|
11873
|
+
const declaresAutoIncrement = /\bAUTOINCREMENT\b/i.test(createSql);
|
|
11874
|
+
const columns = info.rows.map((row) => {
|
|
11875
|
+
const isPrimaryKey = row.pk > 0;
|
|
11876
|
+
const column = {
|
|
11877
|
+
name: row.name,
|
|
11878
|
+
type: row.type || "BLOB",
|
|
11879
|
+
nullable: row.notnull === 0,
|
|
11880
|
+
primaryKey: isPrimaryKey
|
|
11881
|
+
};
|
|
11882
|
+
if (row.dflt_value !== null)
|
|
11883
|
+
column.default = row.dflt_value;
|
|
11884
|
+
const fk = fkByColumn.get(row.name);
|
|
11885
|
+
if (fk)
|
|
11886
|
+
column.foreignKey = fk;
|
|
11887
|
+
if (isPrimaryKey && declaresAutoIncrement && /^INTEGER$/i.test(row.type)) {
|
|
11888
|
+
column.autoIncrement = true;
|
|
11889
|
+
}
|
|
11890
|
+
return column;
|
|
11891
|
+
});
|
|
11892
|
+
const count = await this.execute(`SELECT COUNT(*) AS n FROM ${quoteIdentifier(tableName, "sqlite")}`, [], { sqlMode: "native-read-only" });
|
|
11893
|
+
return {
|
|
11894
|
+
name: tableName,
|
|
11895
|
+
columns,
|
|
11896
|
+
columnCount: columns.length,
|
|
11897
|
+
rowCount: count.rows[0]?.n ?? 0,
|
|
11898
|
+
estimatedRowCount: count.rows[0]?.n ?? 0,
|
|
11899
|
+
tableType: "table"
|
|
11900
|
+
};
|
|
11901
|
+
}
|
|
11902
|
+
}
|
|
11903
|
+
var SQLITE_HEADER = "SQLite format 3\x00";
|
|
11904
|
+
var init_sqlite_adapter = __esm(() => {
|
|
11905
|
+
init_types2();
|
|
11906
|
+
init_sql_adapter_utils();
|
|
11907
|
+
});
|
|
11908
|
+
|
|
11627
11909
|
// src/adapters/server-side-script.ts
|
|
11628
11910
|
function isElasticsearchScriptKey(key, parentKey) {
|
|
11629
11911
|
if (key === "script" || key === "script_fields")
|
|
@@ -14267,6 +14549,8 @@ class AdapterFactory {
|
|
|
14267
14549
|
case "mysql":
|
|
14268
14550
|
case "mariadb":
|
|
14269
14551
|
return new MySQLAdapter(options);
|
|
14552
|
+
case "sqlite":
|
|
14553
|
+
return new SQLiteAdapter(options);
|
|
14270
14554
|
default:
|
|
14271
14555
|
throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
|
|
14272
14556
|
}
|
|
@@ -14290,6 +14574,7 @@ class AdapterFactory {
|
|
|
14290
14574
|
case "postgresql":
|
|
14291
14575
|
case "mysql":
|
|
14292
14576
|
case "mariadb":
|
|
14577
|
+
case "sqlite":
|
|
14293
14578
|
return AdapterFactory.createSqlAdapter(options);
|
|
14294
14579
|
case "redis":
|
|
14295
14580
|
return AdapterFactory.createRedisAdapter(config);
|
|
@@ -14306,6 +14591,7 @@ class AdapterFactory {
|
|
|
14306
14591
|
case "postgresql":
|
|
14307
14592
|
case "mysql":
|
|
14308
14593
|
case "mariadb":
|
|
14594
|
+
case "sqlite":
|
|
14309
14595
|
return AdapterFactory.createSqlAdapter(options);
|
|
14310
14596
|
case "mongodb":
|
|
14311
14597
|
case "redis":
|
|
@@ -14350,6 +14636,7 @@ class AdapterFactory {
|
|
|
14350
14636
|
var init_factory = __esm(() => {
|
|
14351
14637
|
init_postgresql_adapter();
|
|
14352
14638
|
init_mysql_adapter();
|
|
14639
|
+
init_sqlite_adapter();
|
|
14353
14640
|
init_mongodb_adapter();
|
|
14354
14641
|
init_redis_adapter();
|
|
14355
14642
|
init_elasticsearch_adapter();
|
|
@@ -14721,13 +15008,113 @@ var init_init_mongodb = __esm(() => {
|
|
|
14721
15008
|
VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
|
|
14722
15009
|
});
|
|
14723
15010
|
|
|
15011
|
+
// src/commands/init-sqlite.ts
|
|
15012
|
+
import { mkdir as mkdir7 } from "fs/promises";
|
|
15013
|
+
function isMemoryTarget(value) {
|
|
15014
|
+
const trimmed = value.trim().toLowerCase();
|
|
15015
|
+
if (trimmed === ":memory:")
|
|
15016
|
+
return true;
|
|
15017
|
+
if (!trimmed.startsWith("file:"))
|
|
15018
|
+
return false;
|
|
15019
|
+
const withoutScheme = trimmed.slice("file:".length);
|
|
15020
|
+
const [path = "", query = ""] = withoutScheme.split("?");
|
|
15021
|
+
if (path === ":memory:")
|
|
15022
|
+
return true;
|
|
15023
|
+
return new URLSearchParams(query).get("mode") === "memory";
|
|
15024
|
+
}
|
|
15025
|
+
async function handleSQLiteInit(ctx) {
|
|
15026
|
+
const { options, configPath, connectionName, isV2Init, existingConfig } = ctx;
|
|
15027
|
+
const isInteractive = options.interactive !== false && process.stdin.isTTY;
|
|
15028
|
+
let file = options.file?.trim();
|
|
15029
|
+
while (!file && isInteractive) {
|
|
15030
|
+
const input = (await promptUser.text(t("init.prompt_sqlite_file"))).trim();
|
|
15031
|
+
if (!input)
|
|
15032
|
+
continue;
|
|
15033
|
+
if (isMemoryTarget(input)) {
|
|
15034
|
+
console.error(SQLITE_MEMORY_TARGET_MESSAGE);
|
|
15035
|
+
continue;
|
|
15036
|
+
}
|
|
15037
|
+
file = input;
|
|
15038
|
+
}
|
|
15039
|
+
if (!file) {
|
|
15040
|
+
throw new Error(t("errors.sqlite_file_required"));
|
|
15041
|
+
}
|
|
15042
|
+
if (isMemoryTarget(file)) {
|
|
15043
|
+
throw new Error(SQLITE_MEMORY_TARGET_MESSAGE);
|
|
15044
|
+
}
|
|
15045
|
+
const sqliteConfig = { system: "sqlite", file };
|
|
15046
|
+
let permission = options.permission || "query-only";
|
|
15047
|
+
if (isInteractive && !options.permission) {
|
|
15048
|
+
permission = await promptUser.select(t("init.prompt_permission"), [
|
|
15049
|
+
"query-only",
|
|
15050
|
+
"read-write",
|
|
15051
|
+
"data-admin",
|
|
15052
|
+
"admin"
|
|
15053
|
+
]);
|
|
15054
|
+
}
|
|
15055
|
+
if (!VALID_PERMISSIONS2.includes(permission)) {
|
|
15056
|
+
throw new Error(t_vars("errors.invalid_permission", { permission }));
|
|
15057
|
+
}
|
|
15058
|
+
const canProceed = await checkOverwrite(configPath, isInteractive, !!options.force);
|
|
15059
|
+
if (!canProceed)
|
|
15060
|
+
return;
|
|
15061
|
+
if (!options.skipTest) {
|
|
15062
|
+
console.log(t("init.connection_testing"));
|
|
15063
|
+
const adapter = AdapterFactory.createAdapterWithoutRules(sqliteConfig);
|
|
15064
|
+
try {
|
|
15065
|
+
await adapter.connect();
|
|
15066
|
+
await adapter.testConnection();
|
|
15067
|
+
console.log(t("init.connection_success"));
|
|
15068
|
+
} catch (error) {
|
|
15069
|
+
if (error instanceof ConnectionError) {
|
|
15070
|
+
console.error(t_vars("errors.connection_failed", { message: error.message }));
|
|
15071
|
+
console.error(t("init.connection_hints"));
|
|
15072
|
+
error.hints.forEach((hint) => console.error(` \u2022 ${hint}`));
|
|
15073
|
+
process.exit(1);
|
|
15074
|
+
}
|
|
15075
|
+
throw error;
|
|
15076
|
+
} finally {
|
|
15077
|
+
await adapter.disconnect();
|
|
15078
|
+
}
|
|
15079
|
+
}
|
|
15080
|
+
if (isV2Init) {
|
|
15081
|
+
await writeV2InitConfig(configPath, connectionName, sqliteConfig, permission, options.envFile);
|
|
15082
|
+
return;
|
|
15083
|
+
}
|
|
15084
|
+
const newConfig = configModule.merge(existingConfig, {
|
|
15085
|
+
connection: sqliteConfig,
|
|
15086
|
+
permission
|
|
15087
|
+
});
|
|
15088
|
+
const globalConfig = isGlobalConfigPath(configPath);
|
|
15089
|
+
const storagePath = globalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15090
|
+
await mkdir7(storagePath, { recursive: true });
|
|
15091
|
+
await configModule.write(storagePath, newConfig);
|
|
15092
|
+
if (!globalConfig) {
|
|
15093
|
+
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
15094
|
+
await writeProjectBinding(configPath, storagePath);
|
|
15095
|
+
}
|
|
15096
|
+
console.log(t("init.config_saved"));
|
|
15097
|
+
}
|
|
15098
|
+
var VALID_PERMISSIONS2;
|
|
15099
|
+
var init_init_sqlite = __esm(() => {
|
|
15100
|
+
init_message_loader();
|
|
15101
|
+
init_config();
|
|
15102
|
+
init_prompts();
|
|
15103
|
+
init_adapters();
|
|
15104
|
+
init_validation();
|
|
15105
|
+
init_config_binding();
|
|
15106
|
+
init_init_shared();
|
|
15107
|
+
VALID_PERMISSIONS2 = ["query-only", "read-write", "data-admin", "admin"];
|
|
15108
|
+
});
|
|
15109
|
+
|
|
14724
15110
|
// src/commands/init.ts
|
|
14725
15111
|
var exports_init = {};
|
|
14726
15112
|
__export(exports_init, {
|
|
14727
|
-
initCommand: () => initCommand
|
|
15113
|
+
initCommand: () => initCommand,
|
|
15114
|
+
INIT_SYSTEM_CHOICES: () => INIT_SYSTEM_CHOICES
|
|
14728
15115
|
});
|
|
14729
15116
|
import { join as join11 } from "path";
|
|
14730
|
-
import { mkdir as
|
|
15117
|
+
import { mkdir as mkdir8 } from "fs/promises";
|
|
14731
15118
|
async function handleRemove(configPath, name) {
|
|
14732
15119
|
const storagePath = await resolveConfigStoragePath(configPath);
|
|
14733
15120
|
const configFile = Bun.file(join11(storagePath, "config.json"));
|
|
@@ -14818,18 +15205,22 @@ async function initCommandHandler(options, command) {
|
|
|
14818
15205
|
const systemFromCli = typeof options.system === "string" ? options.system : undefined;
|
|
14819
15206
|
let system = systemFromCli ?? envConfig?.system ?? "postgresql";
|
|
14820
15207
|
if (shouldPrompt && !options.system && !envConfig?.system) {
|
|
14821
|
-
system = await promptUser.select(t("init.select_system"), [
|
|
14822
|
-
"postgresql",
|
|
14823
|
-
"mysql",
|
|
14824
|
-
"mariadb",
|
|
14825
|
-
"mongodb",
|
|
14826
|
-
"redis",
|
|
14827
|
-
"elasticsearch"
|
|
14828
|
-
]);
|
|
15208
|
+
system = await promptUser.select(t("init.select_system"), [...INIT_SYSTEM_CHOICES]);
|
|
14829
15209
|
}
|
|
14830
|
-
if (!
|
|
15210
|
+
if (!DATABASE_SYSTEMS.includes(system)) {
|
|
14831
15211
|
throw new Error(t_vars("errors.invalid_system", { system }));
|
|
14832
15212
|
}
|
|
15213
|
+
if (system === "sqlite") {
|
|
15214
|
+
await handleSQLiteInit({
|
|
15215
|
+
options,
|
|
15216
|
+
configPath,
|
|
15217
|
+
connectionName,
|
|
15218
|
+
isV2Init,
|
|
15219
|
+
existingConfig,
|
|
15220
|
+
shouldPrompt
|
|
15221
|
+
});
|
|
15222
|
+
return;
|
|
15223
|
+
}
|
|
14833
15224
|
const defaults = getDefaultsForSystem(system);
|
|
14834
15225
|
if (system === "mongodb") {
|
|
14835
15226
|
await handleMongoDBInit({
|
|
@@ -14870,7 +15261,7 @@ async function initCommandHandler(options, command) {
|
|
|
14870
15261
|
"admin"
|
|
14871
15262
|
]);
|
|
14872
15263
|
}
|
|
14873
|
-
if (!
|
|
15264
|
+
if (!VALID_PERMISSIONS3.includes(permission2)) {
|
|
14874
15265
|
throw new Error(t_vars("errors.invalid_permission", { permission: permission2 }));
|
|
14875
15266
|
}
|
|
14876
15267
|
const newConfig2 = configModule.merge(existingConfig, {
|
|
@@ -14886,7 +15277,7 @@ async function initCommandHandler(options, command) {
|
|
|
14886
15277
|
return;
|
|
14887
15278
|
}
|
|
14888
15279
|
const storagePath2 = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
|
|
14889
|
-
await
|
|
15280
|
+
await mkdir8(storagePath2, { recursive: true });
|
|
14890
15281
|
await configModule.write(storagePath2, newConfig2);
|
|
14891
15282
|
if (!isGlobalConfig) {
|
|
14892
15283
|
await migrateLegacyProjectEnvLocal(configPath, storagePath2);
|
|
@@ -14927,7 +15318,7 @@ async function initCommandHandler(options, command) {
|
|
|
14927
15318
|
"admin"
|
|
14928
15319
|
]);
|
|
14929
15320
|
}
|
|
14930
|
-
if (!
|
|
15321
|
+
if (!VALID_PERMISSIONS3.includes(permission)) {
|
|
14931
15322
|
throw new Error(t_vars("errors.invalid_permission", { permission }));
|
|
14932
15323
|
}
|
|
14933
15324
|
configForWrite = connection;
|
|
@@ -15005,7 +15396,7 @@ async function initCommandHandler(options, command) {
|
|
|
15005
15396
|
return;
|
|
15006
15397
|
}
|
|
15007
15398
|
const storagePath = isGlobalConfig ? configPath : getProjectStoragePath(configPath);
|
|
15008
|
-
await
|
|
15399
|
+
await mkdir8(storagePath, { recursive: true });
|
|
15009
15400
|
await configModule.write(storagePath, newConfig);
|
|
15010
15401
|
if (!isGlobalConfig) {
|
|
15011
15402
|
await migrateLegacyProjectEnvLocal(configPath, storagePath);
|
|
@@ -15013,7 +15404,7 @@ async function initCommandHandler(options, command) {
|
|
|
15013
15404
|
}
|
|
15014
15405
|
console.log(t("init.config_saved"));
|
|
15015
15406
|
}
|
|
15016
|
-
var
|
|
15407
|
+
var INIT_SYSTEM_CHOICES, VALID_PERMISSIONS3, initCommand;
|
|
15017
15408
|
var init_init = __esm(() => {
|
|
15018
15409
|
init_esm();
|
|
15019
15410
|
init_message_loader();
|
|
@@ -15023,12 +15414,15 @@ var init_init = __esm(() => {
|
|
|
15023
15414
|
init_prompts();
|
|
15024
15415
|
init_redaction();
|
|
15025
15416
|
init_adapters();
|
|
15417
|
+
init_types2();
|
|
15026
15418
|
init_config_path();
|
|
15027
15419
|
init_config_binding();
|
|
15028
15420
|
init_init_shared();
|
|
15029
15421
|
init_init_mongodb();
|
|
15030
|
-
|
|
15031
|
-
|
|
15422
|
+
init_init_sqlite();
|
|
15423
|
+
INIT_SYSTEM_CHOICES = DATABASE_SYSTEMS;
|
|
15424
|
+
VALID_PERMISSIONS3 = ["query-only", "read-write", "data-admin", "admin"];
|
|
15425
|
+
initCommand = new Command("init").description("Initialize dbcli configuration with .env parsing and interactive prompts").option("--host <host>", "Database host").option("--port <port>", "Database port").option("--user <user>", "Database user").option("--password <password>", "Database password").option("--name <name>", "Database name").option("--system <system>", "Database system (postgresql, mysql, mariadb, mongodb, redis, elasticsearch)").option("--file <path>", "SQLite database file path (the database must already exist)").option("--cloud-id <id>", "Elasticsearch Cloud ID").option("--api-key <key>", "Elasticsearch API Key").option("--uri <uri>", "MongoDB connection URI (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").option("--permission <permission>", "Permission level (query-only, read-write, data-admin, admin)", "query-only").option("--use-env-refs", "Store env var references in config instead of actual values (for CI/CD or multi-env)", false).option("--env-host <var>", "Env var name for host (with --use-env-refs)").option("--env-port <var>", "Env var name for port (with --use-env-refs)").option("--env-user <var>", "Env var name for user (with --use-env-refs)").option("--env-password <var>", "Env var name for password (with --use-env-refs)").option("--env-database <var>", "Env var name for database (with --use-env-refs)").option("--skip-test", "Skip database connection test").option("--no-interactive", "Non-interactive mode (requires all values via flags)").option("--force", "Skip overwrite confirmation if .dbcli exists").option("--conn-name <name>", "Connection name (creates v2 multi-connection config)").option("--env-file <path>", "Path to env file for this connection").option("--remove <name>", "Remove a named connection").option("--rename <names>", "Rename a connection (format: old:new)").action(async (options) => {
|
|
15032
15426
|
try {
|
|
15033
15427
|
await initCommandHandler(options, initCommand);
|
|
15034
15428
|
} catch (error) {
|
|
@@ -16975,17 +17369,22 @@ var init_formatters = __esm(() => {
|
|
|
16975
17369
|
init_query_result_formatter();
|
|
16976
17370
|
});
|
|
16977
17371
|
|
|
16978
|
-
// src/commands/
|
|
16979
|
-
var exports_list = {};
|
|
16980
|
-
__export(exports_list, {
|
|
16981
|
-
listCommand: () => listCommand
|
|
16982
|
-
});
|
|
17372
|
+
// src/commands/require-sql-connection.ts
|
|
16983
17373
|
function requireSqlConnection(connection) {
|
|
16984
|
-
if (!
|
|
17374
|
+
if (!SQL_DATABASE_SYSTEMS.includes(connection.system)) {
|
|
16985
17375
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
16986
17376
|
}
|
|
16987
17377
|
return connection;
|
|
16988
17378
|
}
|
|
17379
|
+
var init_require_sql_connection = __esm(() => {
|
|
17380
|
+
init_types2();
|
|
17381
|
+
});
|
|
17382
|
+
|
|
17383
|
+
// src/commands/list.ts
|
|
17384
|
+
var exports_list = {};
|
|
17385
|
+
__export(exports_list, {
|
|
17386
|
+
listCommand: () => listCommand
|
|
17387
|
+
});
|
|
16989
17388
|
async function listAction(options, command) {
|
|
16990
17389
|
validateFormat(options.format, ALLOWED_FORMATS, "list");
|
|
16991
17390
|
const config = await configModule.read(resolveConfigPath(command, options));
|
|
@@ -17135,6 +17534,7 @@ var init_list = __esm(() => {
|
|
|
17135
17534
|
init_config_path();
|
|
17136
17535
|
init_validation();
|
|
17137
17536
|
init_connection_selector();
|
|
17537
|
+
init_require_sql_connection();
|
|
17138
17538
|
ALLOWED_FORMATS = ["table", "json"];
|
|
17139
17539
|
listCommand = new Command().name("list").description("List all tables in the database with metadata").option("--format <format>", "Output format: table (default) or json", "table").option("--config <path>", "Path to .dbcli config file", ".dbcli").addOption(createConnectionSelectorOption()).option("--include-system", "Elasticsearch only: include system indices whose names start with dot", false).action(listAction);
|
|
17140
17540
|
});
|
|
@@ -17250,7 +17650,7 @@ var init_artifact = __esm(() => {
|
|
|
17250
17650
|
});
|
|
17251
17651
|
|
|
17252
17652
|
// src/core/verification/artifact-writer.ts
|
|
17253
|
-
import { mkdir as
|
|
17653
|
+
import { mkdir as mkdir9, writeFile as writeFile3, link, unlink as unlink5 } from "fs/promises";
|
|
17254
17654
|
import { join as join12 } from "path";
|
|
17255
17655
|
function pad(n, len = 2) {
|
|
17256
17656
|
return String(n).padStart(len, "0");
|
|
@@ -17269,7 +17669,7 @@ function verificationArtifactFilename(artifact) {
|
|
|
17269
17669
|
}
|
|
17270
17670
|
async function writeVerificationArtifact(storageDir, artifact) {
|
|
17271
17671
|
const dir = join12(storageDir, VERIFICATION_DIR_RELATIVE);
|
|
17272
|
-
await
|
|
17672
|
+
await mkdir9(dir, { recursive: true });
|
|
17273
17673
|
const target = join12(dir, verificationArtifactFilename(artifact));
|
|
17274
17674
|
const tmp = `${target}.${process.pid}.tmp`;
|
|
17275
17675
|
try {
|
|
@@ -17842,7 +18242,7 @@ var init_legacy = __esm(() => {
|
|
|
17842
18242
|
|
|
17843
18243
|
// src/core/evidence-receipt/index.ts
|
|
17844
18244
|
import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
|
|
17845
|
-
import { link as link2, lstat as lstat4, mkdir as
|
|
18245
|
+
import { link as link2, lstat as lstat4, mkdir as mkdir10, readFile as readFile3, realpath, unlink as unlink7, writeFile as writeFile4 } from "fs/promises";
|
|
17846
18246
|
import { basename as basename3, dirname as dirname5, relative, resolve as resolve5, sep as sep3 } from "path";
|
|
17847
18247
|
function exact(value, keys, label) {
|
|
17848
18248
|
if (Object.keys(value).length !== keys.length || Object.keys(value).some((key) => !keys.includes(key))) {
|
|
@@ -18122,7 +18522,7 @@ async function writeEvidenceReceipt(workspaceRoot, outputPath, receipt) {
|
|
|
18122
18522
|
const parent = dirname5(requested);
|
|
18123
18523
|
if (!inside(workspace, await existingAncestor(parent)))
|
|
18124
18524
|
throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
|
|
18125
|
-
await
|
|
18525
|
+
await mkdir10(parent, { recursive: true });
|
|
18126
18526
|
const realParent = await realpath(parent);
|
|
18127
18527
|
if (!inside(workspace, realParent))
|
|
18128
18528
|
throw new EvidenceReceiptValidationError("output path resolves outside the workspace");
|
|
@@ -18216,8 +18616,16 @@ var init_evidence_receipt = __esm(() => {
|
|
|
18216
18616
|
});
|
|
18217
18617
|
|
|
18218
18618
|
// src/core/saved-queries/types.ts
|
|
18219
|
-
var SUPPORTED_CHART_TYPES, SavedQueryError;
|
|
18619
|
+
var ENGINE_TAGS, SUPPORTED_CHART_TYPES, SavedQueryError;
|
|
18220
18620
|
var init_types4 = __esm(() => {
|
|
18621
|
+
ENGINE_TAGS = Object.freeze([
|
|
18622
|
+
"postgres",
|
|
18623
|
+
"mysql",
|
|
18624
|
+
"sqlite",
|
|
18625
|
+
"elasticsearch",
|
|
18626
|
+
"redis",
|
|
18627
|
+
"mongodb"
|
|
18628
|
+
]);
|
|
18221
18629
|
SUPPORTED_CHART_TYPES = ["line", "bar", "area", "pie"];
|
|
18222
18630
|
SavedQueryError = class SavedQueryError extends Error {
|
|
18223
18631
|
code;
|
|
@@ -18311,6 +18719,7 @@ function paramErr(spec, raw, expected) {
|
|
|
18311
18719
|
function rewriteToBind(sqlBody, params, engine) {
|
|
18312
18720
|
const masked = stripCommentsAndStrings(sqlBody);
|
|
18313
18721
|
const order = [];
|
|
18722
|
+
const occurrences = [];
|
|
18314
18723
|
const indexByName = new Map;
|
|
18315
18724
|
let m;
|
|
18316
18725
|
NAME_RE.lastIndex = 0;
|
|
@@ -18329,19 +18738,18 @@ function rewriteToBind(sqlBody, params, engine) {
|
|
|
18329
18738
|
const start = m.index;
|
|
18330
18739
|
const end = start + m[0].length;
|
|
18331
18740
|
outSql += sqlBody.slice(cursor, start);
|
|
18332
|
-
|
|
18333
|
-
|
|
18741
|
+
if (engine === "postgres") {
|
|
18742
|
+
outSql += `$${indexByName.get(name)}`;
|
|
18743
|
+
} else {
|
|
18744
|
+
outSql += "?";
|
|
18745
|
+
occurrences.push(name);
|
|
18746
|
+
}
|
|
18334
18747
|
cursor = end;
|
|
18335
18748
|
}
|
|
18336
18749
|
outSql += sqlBody.slice(cursor);
|
|
18337
|
-
const undeclared =
|
|
18338
|
-
const
|
|
18339
|
-
|
|
18340
|
-
undeclared.push(n);
|
|
18341
|
-
return null;
|
|
18342
|
-
}
|
|
18343
|
-
return params[n];
|
|
18344
|
-
});
|
|
18750
|
+
const undeclared = order.filter((n) => !Object.prototype.hasOwnProperty.call(params, n));
|
|
18751
|
+
const bindOrder = engine === "postgres" ? order : occurrences;
|
|
18752
|
+
const values = bindOrder.map((n) => Object.prototype.hasOwnProperty.call(params, n) ? params[n] : null);
|
|
18345
18753
|
return { sql: outSql, values, undeclared };
|
|
18346
18754
|
}
|
|
18347
18755
|
var NAME_RE;
|
|
@@ -18776,7 +19184,16 @@ function stripCommentsAndStrings2(sql, options = {}) {
|
|
|
18776
19184
|
continue;
|
|
18777
19185
|
}
|
|
18778
19186
|
}
|
|
18779
|
-
if (
|
|
19187
|
+
if (options.dialect === "sqlite" && char === "[") {
|
|
19188
|
+
i++;
|
|
19189
|
+
while (i < sql.length && sql[i] !== "]")
|
|
19190
|
+
i++;
|
|
19191
|
+
if (i < sql.length)
|
|
19192
|
+
i++;
|
|
19193
|
+
result += " ";
|
|
19194
|
+
continue;
|
|
19195
|
+
}
|
|
19196
|
+
if ((mysqlDialect || options.dialect === "sqlite") && char === "`") {
|
|
18780
19197
|
i++;
|
|
18781
19198
|
while (i < sql.length) {
|
|
18782
19199
|
if (sql[i] === "`") {
|
|
@@ -18849,8 +19266,13 @@ function extractFirstKeyword(sql) {
|
|
|
18849
19266
|
}
|
|
18850
19267
|
return firstKeyword;
|
|
18851
19268
|
}
|
|
18852
|
-
function
|
|
19269
|
+
function isDeclaredAdminOnly(keyword) {
|
|
19270
|
+
return DECLARED_ADMIN_ONLY_KEYWORDS.has(keyword.toUpperCase());
|
|
19271
|
+
}
|
|
19272
|
+
function mapKeywordToType(keyword, dialect) {
|
|
18853
19273
|
const upper = keyword.toUpperCase();
|
|
19274
|
+
if (upper === "REPLACE" && dialect === "sqlite")
|
|
19275
|
+
return "REPLACE";
|
|
18854
19276
|
if (upper === "SELECT")
|
|
18855
19277
|
return "SELECT";
|
|
18856
19278
|
if (upper === "SHOW")
|
|
@@ -18880,7 +19302,7 @@ function mapKeywordToType(keyword) {
|
|
|
18880
19302
|
return "UNKNOWN";
|
|
18881
19303
|
}
|
|
18882
19304
|
function isDestructiveOperation(type) {
|
|
18883
|
-
return ["DELETE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
|
|
19305
|
+
return ["DELETE", "REPLACE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT"].includes(type);
|
|
18884
19306
|
}
|
|
18885
19307
|
function extractAllKeywords(sql) {
|
|
18886
19308
|
const upper = sql.toUpperCase();
|
|
@@ -18947,8 +19369,10 @@ function determineConfidence(type, _keyword, _sql) {
|
|
|
18947
19369
|
function removeParameterMarkers(sql) {
|
|
18948
19370
|
return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
|
|
18949
19371
|
}
|
|
19372
|
+
var DECLARED_ADMIN_ONLY_KEYWORDS;
|
|
18950
19373
|
var init_sql_analysis = __esm(() => {
|
|
18951
19374
|
init_sql_lexical();
|
|
19375
|
+
DECLARED_ADMIN_ONLY_KEYWORDS = new Set(["PRAGMA", "VACUUM", "REINDEX"]);
|
|
18952
19376
|
});
|
|
18953
19377
|
|
|
18954
19378
|
// src/core/permission/rank.ts
|
|
@@ -18983,11 +19407,12 @@ __export(exports_permission_guard, {
|
|
|
18983
19407
|
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18984
19408
|
SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
|
|
18985
19409
|
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18986
|
-
PermissionError: () => PermissionError
|
|
19410
|
+
PermissionError: () => PermissionError,
|
|
19411
|
+
ConnectionBoundaryError: () => ConnectionBoundaryError
|
|
18987
19412
|
});
|
|
18988
|
-
function classifyStatement(sql) {
|
|
19413
|
+
function classifyStatement(sql, dialect) {
|
|
18989
19414
|
const normalized = normalizeSQL(sql);
|
|
18990
|
-
const stripped = stripCommentsAndStrings2(normalized);
|
|
19415
|
+
const stripped = stripCommentsAndStrings2(normalized, { dialect });
|
|
18991
19416
|
const upper = stripped.toUpperCase();
|
|
18992
19417
|
if (/^\s*ANALYZE\s+SELECT\b/i.test(stripped)) {
|
|
18993
19418
|
return {
|
|
@@ -19000,7 +19425,16 @@ function classifyStatement(sql) {
|
|
|
19000
19425
|
}
|
|
19001
19426
|
const composite = detectCompositePatterns(upper);
|
|
19002
19427
|
const firstKeyword = extractFirstKeyword(stripped);
|
|
19003
|
-
|
|
19428
|
+
if (isDeclaredAdminOnly(firstKeyword)) {
|
|
19429
|
+
return {
|
|
19430
|
+
type: "UNKNOWN",
|
|
19431
|
+
isDangerous: true,
|
|
19432
|
+
keywords: extractAllKeywords(stripped),
|
|
19433
|
+
isComposite: false,
|
|
19434
|
+
confidence: "HIGH"
|
|
19435
|
+
};
|
|
19436
|
+
}
|
|
19437
|
+
const type = mapKeywordToType(firstKeyword, dialect);
|
|
19004
19438
|
return {
|
|
19005
19439
|
type,
|
|
19006
19440
|
isDangerous: isDestructiveOperation(type),
|
|
@@ -19045,8 +19479,21 @@ function escalateHiddenWrite(sql, classification, dialect) {
|
|
|
19045
19479
|
escalatedFrom: hidden
|
|
19046
19480
|
};
|
|
19047
19481
|
}
|
|
19482
|
+
function reachesAnotherDatabase(sql, dialect) {
|
|
19483
|
+
if (dialect !== "sqlite")
|
|
19484
|
+
return false;
|
|
19485
|
+
return stripCommentsAndStrings2(sql, { dialect }).split(";").some((statement) => SQLITE_CROSS_DATABASE.test(statement));
|
|
19486
|
+
}
|
|
19048
19487
|
function checkPermission(sql, permission, dialect) {
|
|
19049
|
-
|
|
19488
|
+
if (reachesAnotherDatabase(sql, dialect)) {
|
|
19489
|
+
return {
|
|
19490
|
+
allowed: false,
|
|
19491
|
+
reason: "ATTACH and DETACH are refused on a SQLite connection: they reach a database file " + "outside the one this connection names, which the blacklist, audit log and schema " + "cache are all scoped to. Configure a second connection instead.",
|
|
19492
|
+
classification: classifyStatement(sql),
|
|
19493
|
+
boundary: true
|
|
19494
|
+
};
|
|
19495
|
+
}
|
|
19496
|
+
const classification = escalateHiddenWrite(sql, classifyStatement(sql, dialect), dialect);
|
|
19050
19497
|
if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
|
|
19051
19498
|
return {
|
|
19052
19499
|
allowed: false,
|
|
@@ -19101,7 +19548,7 @@ function checkPermissionForClassification(classification, permission) {
|
|
|
19101
19548
|
allowed: false,
|
|
19102
19549
|
reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
|
|
19103
19550
|
classification,
|
|
19104
|
-
requiredPermission:
|
|
19551
|
+
requiredPermission: minimumPermissionFor(classification.type)
|
|
19105
19552
|
};
|
|
19106
19553
|
}
|
|
19107
19554
|
return {
|
|
@@ -19132,16 +19579,28 @@ function enforcePermissionForType(type, permission) {
|
|
|
19132
19579
|
}
|
|
19133
19580
|
function enforcePermission(sql, permission, dialect) {
|
|
19134
19581
|
const result = checkPermission(sql, permission, dialect);
|
|
19582
|
+
if (result.boundary) {
|
|
19583
|
+
throw new ConnectionBoundaryError(result.reason, result.classification);
|
|
19584
|
+
}
|
|
19135
19585
|
if (!result.allowed) {
|
|
19136
19586
|
throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
|
|
19137
19587
|
}
|
|
19138
19588
|
return result.classification;
|
|
19139
19589
|
}
|
|
19140
|
-
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS;
|
|
19590
|
+
var ConnectionBoundaryError, PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, SQLITE_CROSS_DATABASE, TIER_GRANTS;
|
|
19141
19591
|
var init_permission_guard = __esm(() => {
|
|
19142
19592
|
init_message_loader();
|
|
19143
19593
|
init_sql_analysis();
|
|
19144
19594
|
init_rank();
|
|
19595
|
+
ConnectionBoundaryError = class ConnectionBoundaryError extends Error {
|
|
19596
|
+
classification;
|
|
19597
|
+
constructor(message, classification) {
|
|
19598
|
+
super(message);
|
|
19599
|
+
this.classification = classification;
|
|
19600
|
+
this.name = "ConnectionBoundaryError";
|
|
19601
|
+
Object.setPrototypeOf(this, ConnectionBoundaryError.prototype);
|
|
19602
|
+
}
|
|
19603
|
+
};
|
|
19145
19604
|
PermissionError = class PermissionError extends Error {
|
|
19146
19605
|
classification;
|
|
19147
19606
|
requiredPermission;
|
|
@@ -19153,14 +19612,15 @@ var init_permission_guard = __esm(() => {
|
|
|
19153
19612
|
Object.setPrototypeOf(this, PermissionError.prototype);
|
|
19154
19613
|
}
|
|
19155
19614
|
};
|
|
19156
|
-
SQL_DIALECTS = ["postgresql", "mysql", "mariadb"];
|
|
19615
|
+
SQL_DIALECTS = ["postgresql", "mysql", "mariadb", "sqlite"];
|
|
19157
19616
|
SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
|
|
19158
19617
|
SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
|
|
19159
19618
|
ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
|
|
19619
|
+
SQLITE_CROSS_DATABASE = /^\s*(ATTACH|DETACH)\b/i;
|
|
19160
19620
|
TIER_GRANTS = [
|
|
19161
19621
|
{ permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
|
|
19162
19622
|
{ permission: "read-write", types: ["INSERT", "UPDATE"] },
|
|
19163
|
-
{ permission: "data-admin", types: ["DELETE"] }
|
|
19623
|
+
{ permission: "data-admin", types: ["DELETE", "REPLACE"] }
|
|
19164
19624
|
];
|
|
19165
19625
|
});
|
|
19166
19626
|
|
|
@@ -19310,7 +19770,7 @@ var init_mongodb = __esm(() => {
|
|
|
19310
19770
|
});
|
|
19311
19771
|
// src/core/saved-queries/strategies/index.ts
|
|
19312
19772
|
function engineFamily(engine) {
|
|
19313
|
-
if (engine === "postgres" || engine === "mysql")
|
|
19773
|
+
if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
|
|
19314
19774
|
return "sql";
|
|
19315
19775
|
if (engine === "elasticsearch")
|
|
19316
19776
|
return "es";
|
|
@@ -19469,7 +19929,7 @@ function parseScalar(s) {
|
|
|
19469
19929
|
|
|
19470
19930
|
// src/core/saved-queries/parser.ts
|
|
19471
19931
|
function familyOf(engine) {
|
|
19472
|
-
if (engine === "postgres" || engine === "mysql")
|
|
19932
|
+
if (engine === "postgres" || engine === "mysql" || engine === "sqlite")
|
|
19473
19933
|
return "sql";
|
|
19474
19934
|
if (engine === "elasticsearch")
|
|
19475
19935
|
return "es";
|
|
@@ -19795,13 +20255,14 @@ var init_parser = __esm(() => {
|
|
|
19795
20255
|
init_types4();
|
|
19796
20256
|
MAX_BYTES = 64 * 1024;
|
|
19797
20257
|
VALID_TYPES = ["int", "string", "float", "bool", "date", "datetime"];
|
|
19798
|
-
VALID_ENGINES =
|
|
20258
|
+
VALID_ENGINES = ENGINE_TAGS;
|
|
19799
20259
|
INTENT_RE = /^[a-z][a-z0-9.-]*$/;
|
|
19800
20260
|
ENGINE_DIALECT = {
|
|
19801
20261
|
postgres: "postgresql",
|
|
19802
20262
|
postgresql: "postgresql",
|
|
19803
20263
|
mysql: "mysql",
|
|
19804
|
-
mariadb: "mariadb"
|
|
20264
|
+
mariadb: "mariadb",
|
|
20265
|
+
sqlite: "sqlite"
|
|
19805
20266
|
};
|
|
19806
20267
|
});
|
|
19807
20268
|
|
|
@@ -20044,7 +20505,8 @@ var init_read_only = __esm(() => {
|
|
|
20044
20505
|
DIALECT = {
|
|
20045
20506
|
postgresql: "Postgresql",
|
|
20046
20507
|
mysql: "MySQL",
|
|
20047
|
-
mariadb: "MariaDB"
|
|
20508
|
+
mariadb: "MariaDB",
|
|
20509
|
+
sqlite: "Sqlite"
|
|
20048
20510
|
};
|
|
20049
20511
|
WRITE_STATEMENT_TYPES = new Set([
|
|
20050
20512
|
"insert",
|
|
@@ -20390,7 +20852,8 @@ var init_query_draft = __esm(() => {
|
|
|
20390
20852
|
DIALECT2 = {
|
|
20391
20853
|
postgresql: "Postgresql",
|
|
20392
20854
|
mysql: "MySQL",
|
|
20393
|
-
mariadb: "MariaDB"
|
|
20855
|
+
mariadb: "MariaDB",
|
|
20856
|
+
sqlite: "Sqlite"
|
|
20394
20857
|
};
|
|
20395
20858
|
HASH = /^[a-f0-9]{64}$/;
|
|
20396
20859
|
IDENTIFIER = /^[a-z][a-z0-9-]*$/;
|
|
@@ -21312,6 +21775,8 @@ function mapSystemToEngine(system) {
|
|
|
21312
21775
|
return "postgres";
|
|
21313
21776
|
if (system === "mysql" || system === "mariadb")
|
|
21314
21777
|
return "mysql";
|
|
21778
|
+
if (system === "sqlite")
|
|
21779
|
+
return "sqlite";
|
|
21315
21780
|
if (system === "mongodb")
|
|
21316
21781
|
return "mongodb";
|
|
21317
21782
|
if (system === "elasticsearch")
|
|
@@ -22544,13 +23009,25 @@ class BlacklistValidator {
|
|
|
22544
23009
|
const tables = dedupe2(tableNames);
|
|
22545
23010
|
const blacklistedColumns = tables.length === 0 ? this.manager.getAllBlacklistedColumns() : Array.from(new Set(tables.flatMap((table) => this.manager.getBlacklistedColumns(table))));
|
|
22546
23011
|
if (blacklistedColumns.length === 0) {
|
|
22547
|
-
return {
|
|
23012
|
+
return {
|
|
23013
|
+
filteredRows: rows,
|
|
23014
|
+
omittedColumns: [],
|
|
23015
|
+
cost: NO_MASKING_COST,
|
|
23016
|
+
reachesOmitted: () => false
|
|
23017
|
+
};
|
|
22548
23018
|
}
|
|
23019
|
+
let rowsScanned = 0;
|
|
23020
|
+
let keysScanned = 0;
|
|
23021
|
+
let ruleEvaluations = 0;
|
|
23022
|
+
let pathSplits = 0;
|
|
23023
|
+
let nestedProbeRows = 0;
|
|
23024
|
+
let nestedGlobRows = 0;
|
|
22549
23025
|
const probeNested = blacklistedColumns.some((path2) => path2.includes("."));
|
|
22550
23026
|
const presentColumns = new Set(columnList);
|
|
22551
23027
|
const nestedHeads = new Set;
|
|
22552
23028
|
const collect2 = (record4) => {
|
|
22553
23029
|
for (const key of Object.getOwnPropertyNames(record4)) {
|
|
23030
|
+
keysScanned += 1;
|
|
22554
23031
|
presentColumns.add(key);
|
|
22555
23032
|
if (!probeNested)
|
|
22556
23033
|
continue;
|
|
@@ -22560,6 +23037,7 @@ class BlacklistValidator {
|
|
|
22560
23037
|
}
|
|
22561
23038
|
};
|
|
22562
23039
|
for (const row of rows) {
|
|
23040
|
+
rowsScanned += 1;
|
|
22563
23041
|
if (row === null || typeof row !== "object")
|
|
22564
23042
|
continue;
|
|
22565
23043
|
if (Array.isArray(row)) {
|
|
@@ -22582,6 +23060,7 @@ class BlacklistValidator {
|
|
|
22582
23060
|
}
|
|
22583
23061
|
const omitted = new Set;
|
|
22584
23062
|
for (const path2 of blacklistedColumns) {
|
|
23063
|
+
ruleEvaluations += 1;
|
|
22585
23064
|
const present = presentByFolded.get(foldFieldPath(path2));
|
|
22586
23065
|
if (present !== undefined) {
|
|
22587
23066
|
for (const name of present)
|
|
@@ -22593,8 +23072,17 @@ class BlacklistValidator {
|
|
|
22593
23072
|
continue;
|
|
22594
23073
|
if (!nestedHeads.has(foldFieldPath(path2.slice(0, dot))))
|
|
22595
23074
|
continue;
|
|
23075
|
+
pathSplits += 1;
|
|
22596
23076
|
const segments = path2.split(".").map(foldFieldPath);
|
|
22597
|
-
|
|
23077
|
+
let found = false;
|
|
23078
|
+
for (const row of rows) {
|
|
23079
|
+
nestedProbeRows += 1;
|
|
23080
|
+
if (hasFieldPath(row, segments, PRE_FOLDED)) {
|
|
23081
|
+
found = true;
|
|
23082
|
+
break;
|
|
23083
|
+
}
|
|
23084
|
+
}
|
|
23085
|
+
if (found)
|
|
22598
23086
|
omitted.add(path2);
|
|
22599
23087
|
}
|
|
22600
23088
|
const nestedGlobs = globRules.filter((pattern) => pattern.segments.length > 1);
|
|
@@ -22605,6 +23093,7 @@ class BlacklistValidator {
|
|
|
22605
23093
|
for (const row of rows) {
|
|
22606
23094
|
if (pending.size === 0)
|
|
22607
23095
|
break;
|
|
23096
|
+
nestedGlobRows += 1;
|
|
22608
23097
|
if (row === null || typeof row !== "object")
|
|
22609
23098
|
continue;
|
|
22610
23099
|
collectNestedMatches(row, states, pending, (pattern) => {
|
|
@@ -22642,13 +23131,35 @@ class BlacklistValidator {
|
|
|
22642
23131
|
}
|
|
22643
23132
|
const omittedColumns = Array.from(omitted);
|
|
22644
23133
|
if (omittedColumns.length === 0) {
|
|
22645
|
-
return {
|
|
23134
|
+
return {
|
|
23135
|
+
filteredRows: rows,
|
|
23136
|
+
omittedColumns: [],
|
|
23137
|
+
cost: Object.freeze({
|
|
23138
|
+
rowsScanned,
|
|
23139
|
+
keysScanned,
|
|
23140
|
+
ruleEvaluations,
|
|
23141
|
+
pathSplits,
|
|
23142
|
+
nestedProbeRows,
|
|
23143
|
+
nestedGlobRows,
|
|
23144
|
+
nestedGlobMatches: matchedNested.length
|
|
23145
|
+
}),
|
|
23146
|
+
reachesOmitted: () => false
|
|
23147
|
+
};
|
|
22646
23148
|
}
|
|
22647
23149
|
const removalPaths = matchedNested.length === 0 ? omittedColumns : omittedColumns.filter((path2) => !matchedNested.some((p) => p.raw === path2));
|
|
22648
23150
|
const filteredRows = omitFieldPaths(rows, removalPaths, matchedNested.length > 0 ? { ...FOLD_CASE2, patterns: matchedNested } : FOLD_CASE2);
|
|
22649
23151
|
return {
|
|
22650
23152
|
filteredRows,
|
|
22651
23153
|
omittedColumns,
|
|
23154
|
+
cost: Object.freeze({
|
|
23155
|
+
rowsScanned,
|
|
23156
|
+
keysScanned,
|
|
23157
|
+
ruleEvaluations,
|
|
23158
|
+
pathSplits,
|
|
23159
|
+
nestedProbeRows,
|
|
23160
|
+
nestedGlobRows,
|
|
23161
|
+
nestedGlobMatches: matchedNested.length
|
|
23162
|
+
}),
|
|
22652
23163
|
reachesOmitted: (path2) => {
|
|
22653
23164
|
if (omittedByName(removalPaths, path2))
|
|
22654
23165
|
return true;
|
|
@@ -22676,7 +23187,7 @@ class BlacklistValidator {
|
|
|
22676
23187
|
});
|
|
22677
23188
|
}
|
|
22678
23189
|
}
|
|
22679
|
-
var FOLD_CASE2, PRE_FOLDED;
|
|
23190
|
+
var FOLD_CASE2, PRE_FOLDED, NO_MASKING_COST;
|
|
22680
23191
|
var init_blacklist_validator = __esm(() => {
|
|
22681
23192
|
init_glob();
|
|
22682
23193
|
init_blacklist();
|
|
@@ -22687,6 +23198,15 @@ var init_blacklist_validator = __esm(() => {
|
|
|
22687
23198
|
init_es_index_target();
|
|
22688
23199
|
FOLD_CASE2 = { caseInsensitive: true };
|
|
22689
23200
|
PRE_FOLDED = { caseInsensitive: true, alreadyFolded: true };
|
|
23201
|
+
NO_MASKING_COST = Object.freeze({
|
|
23202
|
+
rowsScanned: 0,
|
|
23203
|
+
keysScanned: 0,
|
|
23204
|
+
ruleEvaluations: 0,
|
|
23205
|
+
pathSplits: 0,
|
|
23206
|
+
nestedProbeRows: 0,
|
|
23207
|
+
nestedGlobRows: 0,
|
|
23208
|
+
nestedGlobMatches: 0
|
|
23209
|
+
});
|
|
22690
23210
|
});
|
|
22691
23211
|
|
|
22692
23212
|
// src/core/health-checker.ts
|
|
@@ -22839,7 +23359,7 @@ var init_core = __esm(() => {
|
|
|
22839
23359
|
// src/core/audit/lock.ts
|
|
22840
23360
|
import { hostname } from "os";
|
|
22841
23361
|
import { dirname as dirname7 } from "path";
|
|
22842
|
-
import { mkdir as
|
|
23362
|
+
import { mkdir as mkdir11, open as open2, rm } from "fs/promises";
|
|
22843
23363
|
|
|
22844
23364
|
class AuditLockManager {
|
|
22845
23365
|
auditFilePath;
|
|
@@ -22897,7 +23417,7 @@ class AuditLockManager {
|
|
|
22897
23417
|
}
|
|
22898
23418
|
async tryAcquireLock(operationName) {
|
|
22899
23419
|
try {
|
|
22900
|
-
await
|
|
23420
|
+
await mkdir11(dirname7(this.lockPath), { recursive: true });
|
|
22901
23421
|
const lockFile = Bun.file(this.lockPath);
|
|
22902
23422
|
if (await lockFile.exists()) {
|
|
22903
23423
|
const lockContent = await lockFile.json();
|
|
@@ -22950,7 +23470,7 @@ var init_rotation = __esm(() => {
|
|
|
22950
23470
|
});
|
|
22951
23471
|
|
|
22952
23472
|
// src/core/audit/logger.ts
|
|
22953
|
-
import { appendFile, mkdir as
|
|
23473
|
+
import { appendFile, mkdir as mkdir12, readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
22954
23474
|
import { join as join21 } from "path";
|
|
22955
23475
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
22956
23476
|
|
|
@@ -22999,7 +23519,7 @@ class AuditLogger {
|
|
|
22999
23519
|
try {
|
|
23000
23520
|
const sessionId = await this.sessionIdService.resolve();
|
|
23001
23521
|
this.cachedSessionId = sessionId;
|
|
23002
|
-
await
|
|
23522
|
+
await mkdir12(this.auditDir, { recursive: true });
|
|
23003
23523
|
if (!this.writerInitialized) {
|
|
23004
23524
|
await this.syncCountersFromDisk();
|
|
23005
23525
|
this.writerInitialized = true;
|
|
@@ -23104,7 +23624,7 @@ var init_logger2 = __esm(() => {
|
|
|
23104
23624
|
});
|
|
23105
23625
|
|
|
23106
23626
|
// src/core/audit/session-id.ts
|
|
23107
|
-
import { mkdir as
|
|
23627
|
+
import { mkdir as mkdir13, readFile as readFile5, rename as rename5, stat as stat3, writeFile as writeFile5 } from "fs/promises";
|
|
23108
23628
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
23109
23629
|
import { dirname as dirname8, join as join22 } from "path";
|
|
23110
23630
|
function generateSessionId(pid, nowMs) {
|
|
@@ -23137,7 +23657,7 @@ async function writeSessionIdFile(storagePath, payload) {
|
|
|
23137
23657
|
const target = join22(storagePath, LAST_SESSION_ID_RELATIVE);
|
|
23138
23658
|
const tmp = `${target}.tmp`;
|
|
23139
23659
|
try {
|
|
23140
|
-
await
|
|
23660
|
+
await mkdir13(dirname8(target), { recursive: true });
|
|
23141
23661
|
await writeFile5(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
23142
23662
|
await rename5(tmp, target);
|
|
23143
23663
|
} catch {}
|
|
@@ -23258,6 +23778,18 @@ function tokenize2(sql, dialect, backslashEscapes) {
|
|
|
23258
23778
|
}
|
|
23259
23779
|
continue;
|
|
23260
23780
|
}
|
|
23781
|
+
if (dialect === "sqlite" && char === "[") {
|
|
23782
|
+
i++;
|
|
23783
|
+
let value = "";
|
|
23784
|
+
while (i < sql.length && sql[i] !== "]") {
|
|
23785
|
+
value += sql[i];
|
|
23786
|
+
i++;
|
|
23787
|
+
}
|
|
23788
|
+
if (i < sql.length)
|
|
23789
|
+
i++;
|
|
23790
|
+
tokens.push({ kind: "identifier", value, quoted: true });
|
|
23791
|
+
continue;
|
|
23792
|
+
}
|
|
23261
23793
|
if (char === '"' || char === "`") {
|
|
23262
23794
|
const quote = char;
|
|
23263
23795
|
i++;
|
|
@@ -24808,7 +25340,7 @@ var init_render_markdown = __esm(() => {
|
|
|
24808
25340
|
});
|
|
24809
25341
|
|
|
24810
25342
|
// src/core/recovery/last-envelope.ts
|
|
24811
|
-
import { writeFile as writeFile6, readFile as readFile6, rename as rename6, mkdir as
|
|
25343
|
+
import { writeFile as writeFile6, readFile as readFile6, rename as rename6, mkdir as mkdir14, stat as stat4 } from "fs/promises";
|
|
24812
25344
|
import { dirname as dirname9, join as join23 } from "path";
|
|
24813
25345
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
24814
25346
|
function sanitizeCommandSummary(argv) {
|
|
@@ -24827,7 +25359,7 @@ async function writeLastEnvelope(cwd, envelope, argv, now = () => new Date, id =
|
|
|
24827
25359
|
envelope
|
|
24828
25360
|
};
|
|
24829
25361
|
try {
|
|
24830
|
-
await
|
|
25362
|
+
await mkdir14(dirname9(target), { recursive: true });
|
|
24831
25363
|
await writeFile6(tmp, JSON.stringify(payload, null, 2), "utf8");
|
|
24832
25364
|
await rename6(tmp, target);
|
|
24833
25365
|
} catch {}
|
|
@@ -27770,7 +28302,8 @@ var init_write_gate = __esm(() => {
|
|
|
27770
28302
|
PARSER_DIALECTS = {
|
|
27771
28303
|
mysql: "MySQL",
|
|
27772
28304
|
mariadb: "MariaDB",
|
|
27773
|
-
postgresql: "Postgresql"
|
|
28305
|
+
postgresql: "Postgresql",
|
|
28306
|
+
sqlite: "Sqlite"
|
|
27774
28307
|
};
|
|
27775
28308
|
WRITE_TYPES = new Set([
|
|
27776
28309
|
"INSERT",
|
|
@@ -28080,12 +28613,6 @@ function mongoCollectionRefs(query) {
|
|
|
28080
28613
|
return [];
|
|
28081
28614
|
}
|
|
28082
28615
|
}
|
|
28083
|
-
function requireSqlConnection2(connection) {
|
|
28084
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
28085
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
28086
|
-
}
|
|
28087
|
-
return connection;
|
|
28088
|
-
}
|
|
28089
28616
|
async function queryCommand(sql, options, command) {
|
|
28090
28617
|
return executeQueryCommand(sql, options, command);
|
|
28091
28618
|
}
|
|
@@ -28437,7 +28964,7 @@ function queryAuditTarget(system, query, options) {
|
|
|
28437
28964
|
async function sqlQueryBranch(query, options, context, fieldSelection) {
|
|
28438
28965
|
const { config } = context;
|
|
28439
28966
|
const effectivePermission = context.effectivePermission ?? config.permission;
|
|
28440
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
28967
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
28441
28968
|
let executionError;
|
|
28442
28969
|
try {
|
|
28443
28970
|
await adapter.connect();
|
|
@@ -28704,6 +29231,7 @@ var init_query = __esm(() => {
|
|
|
28704
29231
|
init_query_fanout();
|
|
28705
29232
|
init_elasticsearch2();
|
|
28706
29233
|
init_slow_query_advisory();
|
|
29234
|
+
init_require_sql_connection();
|
|
28707
29235
|
ALLOWED_FORMATS3 = ["table", "json", "csv"];
|
|
28708
29236
|
defaultQueryCommandRuntime = {
|
|
28709
29237
|
loadConfig: (configPath, connectionName) => configModule.read(configPath, connectionName, { loadLayeredSchema: false }),
|
|
@@ -29856,7 +30384,7 @@ class DataExecutor {
|
|
|
29856
30384
|
return this.dbSystem;
|
|
29857
30385
|
}
|
|
29858
30386
|
getQuoteChar() {
|
|
29859
|
-
return this.dbSystem === "mysql" ? "`" : '"';
|
|
30387
|
+
return this.dbSystem === "mysql" || this.dbSystem === "mariadb" ? "`" : '"';
|
|
29860
30388
|
}
|
|
29861
30389
|
checkBlacklist(operation, tableName2) {
|
|
29862
30390
|
this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
|
|
@@ -30846,12 +31374,6 @@ __export(exports_insert, {
|
|
|
30846
31374
|
insertCommand: () => insertCommand
|
|
30847
31375
|
});
|
|
30848
31376
|
import crypto5 from "crypto";
|
|
30849
|
-
function requireSqlConnection3(connection) {
|
|
30850
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
30851
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
30852
|
-
}
|
|
30853
|
-
return connection;
|
|
30854
|
-
}
|
|
30855
31377
|
async function readStdinJSON() {
|
|
30856
31378
|
try {
|
|
30857
31379
|
if (typeof Bun !== "undefined" && Bun.stdin) {
|
|
@@ -31033,11 +31555,11 @@ async function insertCommand(table, options, command) {
|
|
|
31033
31555
|
await adapter2.disconnect();
|
|
31034
31556
|
}
|
|
31035
31557
|
}
|
|
31036
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
31558
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31037
31559
|
await adapter.connect();
|
|
31038
31560
|
try {
|
|
31039
31561
|
const schema = await adapter.getTableSchema(table);
|
|
31040
|
-
const dbSystem = config.connection.system
|
|
31562
|
+
const dbSystem = config.connection.system;
|
|
31041
31563
|
const blacklistManager = new BlacklistManager(config);
|
|
31042
31564
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
31043
31565
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
@@ -31114,6 +31636,7 @@ var init_insert = __esm(() => {
|
|
|
31114
31636
|
init_dml_plan();
|
|
31115
31637
|
init_dml_plan4();
|
|
31116
31638
|
init_integration_helper();
|
|
31639
|
+
init_require_sql_connection();
|
|
31117
31640
|
});
|
|
31118
31641
|
|
|
31119
31642
|
// src/commands/update.ts
|
|
@@ -31122,12 +31645,6 @@ __export(exports_update, {
|
|
|
31122
31645
|
updateCommand: () => updateCommand
|
|
31123
31646
|
});
|
|
31124
31647
|
import crypto6 from "crypto";
|
|
31125
|
-
function requireSqlConnection4(connection) {
|
|
31126
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
31127
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
31128
|
-
}
|
|
31129
|
-
return connection;
|
|
31130
|
-
}
|
|
31131
31648
|
async function updateCommand(table, options, command) {
|
|
31132
31649
|
let config;
|
|
31133
31650
|
try {
|
|
@@ -31341,7 +31858,7 @@ async function updateCommand(table, options, command) {
|
|
|
31341
31858
|
} catch (error) {
|
|
31342
31859
|
throw new Error(`WHERE clause parsing failed: ${error.message}`);
|
|
31343
31860
|
}
|
|
31344
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
31861
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31345
31862
|
await adapter.connect();
|
|
31346
31863
|
try {
|
|
31347
31864
|
const schema = await adapter.getTableSchema(table);
|
|
@@ -31359,7 +31876,7 @@ async function updateCommand(table, options, command) {
|
|
|
31359
31876
|
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
|
|
31360
31877
|
return;
|
|
31361
31878
|
}
|
|
31362
|
-
const dbSystem = config.connection.system
|
|
31879
|
+
const dbSystem = config.connection.system;
|
|
31363
31880
|
const blacklistManager = new BlacklistManager(config);
|
|
31364
31881
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
31365
31882
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
@@ -31440,6 +31957,7 @@ var init_update = __esm(() => {
|
|
|
31440
31957
|
init_config_path();
|
|
31441
31958
|
init_dml_plan4();
|
|
31442
31959
|
init_integration_helper();
|
|
31960
|
+
init_require_sql_connection();
|
|
31443
31961
|
});
|
|
31444
31962
|
|
|
31445
31963
|
// src/commands/delete.ts
|
|
@@ -31448,12 +31966,6 @@ __export(exports_delete, {
|
|
|
31448
31966
|
deleteCommand: () => deleteCommand
|
|
31449
31967
|
});
|
|
31450
31968
|
import crypto7 from "crypto";
|
|
31451
|
-
function requireSqlConnection5(connection) {
|
|
31452
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
31453
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
31454
|
-
}
|
|
31455
|
-
return connection;
|
|
31456
|
-
}
|
|
31457
31969
|
async function deleteCommand(table, options, command) {
|
|
31458
31970
|
let config;
|
|
31459
31971
|
try {
|
|
@@ -31641,7 +32153,7 @@ async function deleteCommand(table, options, command) {
|
|
|
31641
32153
|
} catch (error) {
|
|
31642
32154
|
throw new Error(`WHERE clause parsing failed: ${error.message}`);
|
|
31643
32155
|
}
|
|
31644
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
32156
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31645
32157
|
await adapter.connect();
|
|
31646
32158
|
try {
|
|
31647
32159
|
const schema = await adapter.getTableSchema(table);
|
|
@@ -31659,7 +32171,7 @@ async function deleteCommand(table, options, command) {
|
|
|
31659
32171
|
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
|
|
31660
32172
|
return;
|
|
31661
32173
|
}
|
|
31662
|
-
const dbSystem = config.connection.system
|
|
32174
|
+
const dbSystem = config.connection.system;
|
|
31663
32175
|
const blacklistManager = new BlacklistManager(config);
|
|
31664
32176
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
31665
32177
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
@@ -31740,6 +32252,7 @@ var init_delete = __esm(() => {
|
|
|
31740
32252
|
init_config_path();
|
|
31741
32253
|
init_dml_plan4();
|
|
31742
32254
|
init_integration_helper();
|
|
32255
|
+
init_require_sql_connection();
|
|
31743
32256
|
});
|
|
31744
32257
|
|
|
31745
32258
|
// src/adapters/elasticsearch/scroll-reader.ts
|
|
@@ -31782,12 +32295,6 @@ __export(exports_export, {
|
|
|
31782
32295
|
buildEsExportRows: () => buildEsExportRows
|
|
31783
32296
|
});
|
|
31784
32297
|
import crypto8 from "crypto";
|
|
31785
|
-
function requireSqlConnection6(connection) {
|
|
31786
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
31787
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
31788
|
-
}
|
|
31789
|
-
return connection;
|
|
31790
|
-
}
|
|
31791
32298
|
async function exportCommand(sql, options, command) {
|
|
31792
32299
|
let config;
|
|
31793
32300
|
try {
|
|
@@ -31818,7 +32325,7 @@ async function exportCommand(sql, options, command) {
|
|
|
31818
32325
|
if (options.format === "jsonl") {
|
|
31819
32326
|
throw new Error("--format jsonl is only supported on MongoDB connections");
|
|
31820
32327
|
}
|
|
31821
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
32328
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
31822
32329
|
await adapter.connect();
|
|
31823
32330
|
const blacklistValidator = new BlacklistValidator(new BlacklistManager(config));
|
|
31824
32331
|
const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, {
|
|
@@ -32178,6 +32685,7 @@ var init_export = __esm(() => {
|
|
|
32178
32685
|
init_field_masker();
|
|
32179
32686
|
init_scroll_reader();
|
|
32180
32687
|
init_collection_references();
|
|
32688
|
+
init_require_sql_connection();
|
|
32181
32689
|
SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
|
|
32182
32690
|
});
|
|
32183
32691
|
|
|
@@ -33516,8 +34024,8 @@ async function ensureDir(dirPath) {
|
|
|
33516
34024
|
await $`mkdir -p ${dirPath}`.quiet();
|
|
33517
34025
|
} catch {
|
|
33518
34026
|
try {
|
|
33519
|
-
const { mkdir:
|
|
33520
|
-
await
|
|
34027
|
+
const { mkdir: mkdir15 } = await import("fs/promises");
|
|
34028
|
+
await mkdir15(dirPath, { recursive: true });
|
|
33521
34029
|
} catch {
|
|
33522
34030
|
throw new Error(`Cannot create directory: ${dirPath}`);
|
|
33523
34031
|
}
|
|
@@ -35355,7 +35863,7 @@ var exports_check = {};
|
|
|
35355
35863
|
__export(exports_check, {
|
|
35356
35864
|
checkCommand: () => checkCommand
|
|
35357
35865
|
});
|
|
35358
|
-
function
|
|
35866
|
+
function requireSqlConnection2(connection) {
|
|
35359
35867
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
35360
35868
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
35361
35869
|
}
|
|
@@ -35367,7 +35875,7 @@ async function checkAction(table, options) {
|
|
|
35367
35875
|
if (!config.connection) {
|
|
35368
35876
|
throw new Error("Database not configured. Run: dbcli init");
|
|
35369
35877
|
}
|
|
35370
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
35878
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection2(config.connection));
|
|
35371
35879
|
await adapter.connect();
|
|
35372
35880
|
try {
|
|
35373
35881
|
const checker = new HealthChecker(adapter);
|
|
@@ -37197,7 +37705,8 @@ var init_ddl = __esm(() => {
|
|
|
37197
37705
|
DIALECT3 = {
|
|
37198
37706
|
mysql: "MySQL",
|
|
37199
37707
|
mariadb: "MariaDB",
|
|
37200
|
-
postgresql: "Postgresql"
|
|
37708
|
+
postgresql: "Postgresql",
|
|
37709
|
+
sqlite: "Sqlite"
|
|
37201
37710
|
};
|
|
37202
37711
|
});
|
|
37203
37712
|
|
|
@@ -37900,7 +38409,7 @@ __export(exports_diff, {
|
|
|
37900
38409
|
ORM_ALIASES: () => ORM_ALIASES
|
|
37901
38410
|
});
|
|
37902
38411
|
import crypto9 from "crypto";
|
|
37903
|
-
function
|
|
38412
|
+
function requireSqlConnection3(connection) {
|
|
37904
38413
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
37905
38414
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
37906
38415
|
}
|
|
@@ -38067,7 +38576,7 @@ async function diffAction(options) {
|
|
|
38067
38576
|
console.error("Elasticsearch \u4E0D\u652F\u63F4 diff \u6307\u4EE4\uFF1B\u5982\u9700\u8FFD\u8E64 mapping \u8B8A\u5316\uFF0C\u8ACB\u6539\u7528 schema \u4E26\u81EA\u884C\u7248\u672C\u5316 mapping JSON");
|
|
38068
38577
|
process.exit(1);
|
|
38069
38578
|
}
|
|
38070
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
38579
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection3(config.connection));
|
|
38071
38580
|
await adapter.connect();
|
|
38072
38581
|
try {
|
|
38073
38582
|
const tables = await adapter.listTables();
|
|
@@ -38193,9 +38702,11 @@ var init_status2 = __esm(() => {
|
|
|
38193
38702
|
const blacklistTables = config.blacklist?.tables ?? [];
|
|
38194
38703
|
const blacklistColumns = config.blacklist?.columns ?? {};
|
|
38195
38704
|
const columnCount = Object.values(blacklistColumns).reduce((sum, cols) => sum + cols.length, 0);
|
|
38705
|
+
const file = config.connection.file;
|
|
38196
38706
|
const status2 = {
|
|
38197
38707
|
permission: config.permission,
|
|
38198
38708
|
system: config.connection.system,
|
|
38709
|
+
...file !== undefined && { file },
|
|
38199
38710
|
blacklist: {
|
|
38200
38711
|
tables: blacklistTables.length,
|
|
38201
38712
|
columns: columnCount
|
|
@@ -38205,6 +38716,8 @@ var init_status2 = __esm(() => {
|
|
|
38205
38716
|
if (options.format === "text") {
|
|
38206
38717
|
console.log(`Permission: ${status2.permission}`);
|
|
38207
38718
|
console.log(`System: ${status2.system}`);
|
|
38719
|
+
if (file !== undefined)
|
|
38720
|
+
console.log(`File: ${file}`);
|
|
38208
38721
|
console.log(`Blacklist: ${status2.blacklist.tables} table(s), ${status2.blacklist.columns} column(s)`);
|
|
38209
38722
|
console.log(`Version: ${status2.version}`);
|
|
38210
38723
|
} else {
|
|
@@ -39941,7 +40454,8 @@ var init_parse_sql = __esm(() => {
|
|
|
39941
40454
|
DIALECT4 = {
|
|
39942
40455
|
mysql: "MySQL",
|
|
39943
40456
|
mariadb: "MariaDB",
|
|
39944
|
-
postgresql: "Postgresql"
|
|
40457
|
+
postgresql: "Postgresql",
|
|
40458
|
+
sqlite: "Sqlite"
|
|
39945
40459
|
};
|
|
39946
40460
|
});
|
|
39947
40461
|
|
|
@@ -41821,18 +42335,14 @@ __export(exports_doctor, {
|
|
|
41821
42335
|
resolveSchemaLastUpdated: () => resolveSchemaLastUpdated,
|
|
41822
42336
|
renderMongoSamplingLine: () => renderMongoSamplingLine,
|
|
41823
42337
|
doctorCommand: () => doctorCommand,
|
|
42338
|
+
collectSQLiteDoctorResults: () => collectSQLiteDoctorResults,
|
|
41824
42339
|
collectMongoDoctorResults: () => collectMongoDoctorResults,
|
|
41825
42340
|
collectElasticsearchDoctorResults: () => collectElasticsearchDoctorResults,
|
|
41826
42341
|
buildDoctorRemediationPlan: () => buildDoctorRemediationPlan
|
|
41827
42342
|
});
|
|
41828
42343
|
import { join as join37 } from "path";
|
|
41829
42344
|
import { resolveSrv as resolveSrv2 } from "dns/promises";
|
|
41830
|
-
|
|
41831
|
-
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
41832
|
-
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
41833
|
-
}
|
|
41834
|
-
return connection;
|
|
41835
|
-
}
|
|
42345
|
+
import { access, constants } from "fs/promises";
|
|
41836
42346
|
function renderMongoSamplingLine(meta) {
|
|
41837
42347
|
if (!meta.sampleMethod)
|
|
41838
42348
|
return "";
|
|
@@ -42072,6 +42582,46 @@ async function collectElasticsearchDoctorResults(config, runtimeOverrides = {})
|
|
|
42072
42582
|
}
|
|
42073
42583
|
return results;
|
|
42074
42584
|
}
|
|
42585
|
+
async function collectSQLiteDoctorResults(config, runtimeOverrides = {}) {
|
|
42586
|
+
const results = [];
|
|
42587
|
+
const file = config.connection.file ?? "";
|
|
42588
|
+
const fileResults = await runDoctorChecks.checkSQLiteFile(file, config.permission ?? "query-only");
|
|
42589
|
+
results.push(...fileResults);
|
|
42590
|
+
if (fileResults.some((r) => r.status === "error"))
|
|
42591
|
+
return results;
|
|
42592
|
+
const adapter = AdapterFactory.createSqlAdapter(config.connection);
|
|
42593
|
+
try {
|
|
42594
|
+
await adapter.connect();
|
|
42595
|
+
results.push({
|
|
42596
|
+
group: "Connection & Data",
|
|
42597
|
+
label: "Connection",
|
|
42598
|
+
status: "pass",
|
|
42599
|
+
message: `Connected to sqlite ${file}`
|
|
42600
|
+
});
|
|
42601
|
+
try {
|
|
42602
|
+
const tables = await adapter.listTables();
|
|
42603
|
+
const tableColumns = new Map;
|
|
42604
|
+
for (const t2 of tables) {
|
|
42605
|
+
tableColumns.set(t2.name, t2.columns.map((c) => c.name));
|
|
42606
|
+
}
|
|
42607
|
+
if (config.blacklistedColumns) {
|
|
42608
|
+
results.push(runDoctorChecks.checkBlacklistCompleteness(tableColumns, config.blacklistedColumns));
|
|
42609
|
+
}
|
|
42610
|
+
results.push(runDoctorChecks.checkLargeTables(tables, "sqlite"));
|
|
42611
|
+
} catch {}
|
|
42612
|
+
results.push(runDoctorChecks.checkSchemaCacheFreshness(config.metadata?.schemaLastUpdated ?? null));
|
|
42613
|
+
} catch (error) {
|
|
42614
|
+
results.push({
|
|
42615
|
+
group: "Connection & Data",
|
|
42616
|
+
label: "Connection",
|
|
42617
|
+
status: "error",
|
|
42618
|
+
message: `Connection failed: ${error.message}`
|
|
42619
|
+
});
|
|
42620
|
+
} finally {
|
|
42621
|
+
await adapter.disconnect();
|
|
42622
|
+
}
|
|
42623
|
+
return results;
|
|
42624
|
+
}
|
|
42075
42625
|
var ALLOWED_FORMATS15, SENSITIVE_PATTERNS, runDoctorChecks, defaultDoctorCollectorRuntime, doctorCommand;
|
|
42076
42626
|
var init_doctor = __esm(() => {
|
|
42077
42627
|
init_esm();
|
|
@@ -42091,6 +42641,7 @@ var init_doctor = __esm(() => {
|
|
|
42091
42641
|
init_runtime_info();
|
|
42092
42642
|
init_config();
|
|
42093
42643
|
init_integration_helper();
|
|
42644
|
+
init_require_sql_connection();
|
|
42094
42645
|
ALLOWED_FORMATS15 = ["text", "json"];
|
|
42095
42646
|
SENSITIVE_PATTERNS = [
|
|
42096
42647
|
"password",
|
|
@@ -42324,6 +42875,83 @@ var init_doctor = __esm(() => {
|
|
|
42324
42875
|
}
|
|
42325
42876
|
}
|
|
42326
42877
|
},
|
|
42878
|
+
async checkSQLiteFile(file, permission) {
|
|
42879
|
+
const group = "Connection & Data";
|
|
42880
|
+
if (!file) {
|
|
42881
|
+
return [
|
|
42882
|
+
{
|
|
42883
|
+
group,
|
|
42884
|
+
label: "Database file",
|
|
42885
|
+
status: "error",
|
|
42886
|
+
message: 'Connection has no file path \u2014 run "dbcli init --system sqlite"'
|
|
42887
|
+
}
|
|
42888
|
+
];
|
|
42889
|
+
}
|
|
42890
|
+
try {
|
|
42891
|
+
await access(file, constants.F_OK);
|
|
42892
|
+
} catch {
|
|
42893
|
+
return [
|
|
42894
|
+
{
|
|
42895
|
+
group,
|
|
42896
|
+
label: "Database file",
|
|
42897
|
+
status: "error",
|
|
42898
|
+
message: `Database file not found: ${file}`,
|
|
42899
|
+
details: { file }
|
|
42900
|
+
}
|
|
42901
|
+
];
|
|
42902
|
+
}
|
|
42903
|
+
try {
|
|
42904
|
+
await access(file, constants.R_OK);
|
|
42905
|
+
} catch {
|
|
42906
|
+
return [
|
|
42907
|
+
{
|
|
42908
|
+
group,
|
|
42909
|
+
label: "Database file",
|
|
42910
|
+
status: "error",
|
|
42911
|
+
message: `Database file is not readable: ${file}`,
|
|
42912
|
+
details: { file }
|
|
42913
|
+
}
|
|
42914
|
+
];
|
|
42915
|
+
}
|
|
42916
|
+
const results = [
|
|
42917
|
+
{
|
|
42918
|
+
group,
|
|
42919
|
+
label: "Database file",
|
|
42920
|
+
status: "pass",
|
|
42921
|
+
message: `Readable: ${file}`,
|
|
42922
|
+
details: { file }
|
|
42923
|
+
}
|
|
42924
|
+
];
|
|
42925
|
+
if (permission === "query-only") {
|
|
42926
|
+
results.push({
|
|
42927
|
+
group,
|
|
42928
|
+
label: "Database file writable",
|
|
42929
|
+
status: "pass",
|
|
42930
|
+
message: "Not required \u2014 the connection is query-only",
|
|
42931
|
+
details: { file, permission }
|
|
42932
|
+
});
|
|
42933
|
+
return results;
|
|
42934
|
+
}
|
|
42935
|
+
try {
|
|
42936
|
+
await access(file, constants.W_OK);
|
|
42937
|
+
results.push({
|
|
42938
|
+
group,
|
|
42939
|
+
label: "Database file writable",
|
|
42940
|
+
status: "pass",
|
|
42941
|
+
message: `Writable: ${file}`,
|
|
42942
|
+
details: { file, permission }
|
|
42943
|
+
});
|
|
42944
|
+
} catch {
|
|
42945
|
+
results.push({
|
|
42946
|
+
group,
|
|
42947
|
+
label: "Database file writable",
|
|
42948
|
+
status: "error",
|
|
42949
|
+
message: `Database file is not writable, but permission '${permission}' allows writes: ${file}`,
|
|
42950
|
+
details: { file, permission }
|
|
42951
|
+
});
|
|
42952
|
+
}
|
|
42953
|
+
return results;
|
|
42954
|
+
},
|
|
42327
42955
|
checkLargeTables(tables, system = "postgresql") {
|
|
42328
42956
|
const large = tables.filter((t2) => (t2.estimatedRowCount ?? 0) > 1e6);
|
|
42329
42957
|
if (large.length === 0) {
|
|
@@ -42470,13 +43098,18 @@ var init_doctor = __esm(() => {
|
|
|
42470
43098
|
...config,
|
|
42471
43099
|
blacklistedColumns
|
|
42472
43100
|
}));
|
|
43101
|
+
} else if (config.connection.system === "sqlite") {
|
|
43102
|
+
results.push(...await collectSQLiteDoctorResults({
|
|
43103
|
+
...config,
|
|
43104
|
+
blacklistedColumns
|
|
43105
|
+
}));
|
|
42473
43106
|
} else if (config.connection.system === "elasticsearch") {
|
|
42474
43107
|
results.push(...await collectElasticsearchDoctorResults({
|
|
42475
43108
|
...config,
|
|
42476
43109
|
blacklistedColumns
|
|
42477
43110
|
}));
|
|
42478
43111
|
} else {
|
|
42479
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
43112
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection(config.connection));
|
|
42480
43113
|
await adapter.connect();
|
|
42481
43114
|
results.push({
|
|
42482
43115
|
group: "Connection & Data",
|
|
@@ -42792,7 +43425,7 @@ var exports_queries_rename = {};
|
|
|
42792
43425
|
__export(exports_queries_rename, {
|
|
42793
43426
|
queriesRename: () => queriesRename
|
|
42794
43427
|
});
|
|
42795
|
-
import { rename as rename7, mkdir as
|
|
43428
|
+
import { rename as rename7, mkdir as mkdir15 } from "fs/promises";
|
|
42796
43429
|
import { dirname as dirname12 } from "path";
|
|
42797
43430
|
async function queriesRename(oldName, newName, options = {}) {
|
|
42798
43431
|
if (!oldName.startsWith("@") || !newName.startsWith("@")) {
|
|
@@ -42814,7 +43447,7 @@ async function queriesRename(oldName, newName, options = {}) {
|
|
|
42814
43447
|
for (const v of local) {
|
|
42815
43448
|
const dst = snippetKeyToFile(cwd, newName, "local");
|
|
42816
43449
|
const dstWithSuffix = preserveEngineSuffix(v.query.file, dst);
|
|
42817
|
-
await
|
|
43450
|
+
await mkdir15(dirname12(dstWithSuffix), { recursive: true });
|
|
42818
43451
|
await rename7(v.query.file, dstWithSuffix);
|
|
42819
43452
|
await rewriteFrontmatterName(dstWithSuffix, newName.slice(1));
|
|
42820
43453
|
console.log(`renamed ${v.query.file} \u2192 ${dstWithSuffix}`);
|
|
@@ -42840,7 +43473,7 @@ var exports_queries_copy = {};
|
|
|
42840
43473
|
__export(exports_queries_copy, {
|
|
42841
43474
|
queriesCopy: () => queriesCopy
|
|
42842
43475
|
});
|
|
42843
|
-
import { mkdir as
|
|
43476
|
+
import { mkdir as mkdir16, copyFile } from "fs/promises";
|
|
42844
43477
|
import { dirname as dirname13, basename as basename5 } from "path";
|
|
42845
43478
|
async function queriesCopy(src, dst, options = {}) {
|
|
42846
43479
|
if (!src.startsWith("@") || !dst.startsWith("@")) {
|
|
@@ -42859,7 +43492,7 @@ async function queriesCopy(src, dst, options = {}) {
|
|
|
42859
43492
|
}
|
|
42860
43493
|
for (const v of variants) {
|
|
42861
43494
|
const dstFile = mapEngineSuffix(v.query.file, snippetKeyToFile(cwd, dst, "local"));
|
|
42862
|
-
await
|
|
43495
|
+
await mkdir16(dirname13(dstFile), { recursive: true });
|
|
42863
43496
|
await copyFile(v.query.file, dstFile);
|
|
42864
43497
|
console.log(`copied ${v.query.file} \u2192 ${dstFile}`);
|
|
42865
43498
|
}
|
|
@@ -42877,7 +43510,7 @@ var exports_queries_import = {};
|
|
|
42877
43510
|
__export(exports_queries_import, {
|
|
42878
43511
|
queriesImport: () => queriesImport
|
|
42879
43512
|
});
|
|
42880
|
-
import { stat as stat8, mkdir as
|
|
43513
|
+
import { stat as stat8, mkdir as mkdir17, copyFile as copyFile2 } from "fs/promises";
|
|
42881
43514
|
import { basename as basename6, join as join38, extname as extname2 } from "path";
|
|
42882
43515
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
42883
43516
|
async function queriesImport(filePath, options = {}) {
|
|
@@ -42891,7 +43524,7 @@ async function queriesImport(filePath, options = {}) {
|
|
|
42891
43524
|
const key = "@" + baseName;
|
|
42892
43525
|
parseSavedQuery({ key, file: filePath, source: "local", text: text4 });
|
|
42893
43526
|
const targetDir = join38(cwd, ".dbcli/queries");
|
|
42894
|
-
await
|
|
43527
|
+
await mkdir17(targetDir, { recursive: true });
|
|
42895
43528
|
const target = join38(targetDir, basename6(filePath));
|
|
42896
43529
|
if (await Bun.file(target).exists()) {
|
|
42897
43530
|
if (!options.force) {
|
|
@@ -42956,7 +43589,7 @@ __export(exports_queries, {
|
|
|
42956
43589
|
queriesCheck: () => queriesCheck,
|
|
42957
43590
|
listSnippetsForEngine: () => listSnippetsForEngine
|
|
42958
43591
|
});
|
|
42959
|
-
import { mkdir as
|
|
43592
|
+
import { mkdir as mkdir18, writeFile as writeFile8 } from "fs/promises";
|
|
42960
43593
|
import { dirname as dirname14 } from "path";
|
|
42961
43594
|
import { spawn } from "child_process";
|
|
42962
43595
|
async function deriveEngine(command) {
|
|
@@ -43077,7 +43710,7 @@ async function queriesNew(name, options) {
|
|
|
43077
43710
|
process.exit(1);
|
|
43078
43711
|
return;
|
|
43079
43712
|
}
|
|
43080
|
-
await
|
|
43713
|
+
await mkdir18(dirname14(file), { recursive: true });
|
|
43081
43714
|
await writeFile8(file, scaffold(name), "utf8");
|
|
43082
43715
|
console.log(`Created ${file}`);
|
|
43083
43716
|
if (source === "shared")
|
|
@@ -43153,7 +43786,7 @@ async function queriesSearch(keywords, options, command) {
|
|
|
43153
43786
|
process.exit(2);
|
|
43154
43787
|
return;
|
|
43155
43788
|
}
|
|
43156
|
-
const allowed = [
|
|
43789
|
+
const allowed = [...ENGINE_TAGS, "all"];
|
|
43157
43790
|
if (options.engine && !allowed.includes(options.engine)) {
|
|
43158
43791
|
console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
|
|
43159
43792
|
process.exit(2);
|
|
@@ -43245,7 +43878,7 @@ async function queriesSuggest(intent, options, command) {
|
|
|
43245
43878
|
process.exit(2);
|
|
43246
43879
|
return;
|
|
43247
43880
|
}
|
|
43248
|
-
const allowed = [
|
|
43881
|
+
const allowed = [...ENGINE_TAGS, "all"];
|
|
43249
43882
|
if (options.engine && !allowed.includes(options.engine)) {
|
|
43250
43883
|
console.error(`Unknown engine '${options.engine}'. Allowed: ${allowed.join(", ")}.`);
|
|
43251
43884
|
process.exit(2);
|
|
@@ -43854,7 +44487,8 @@ var init_parse = __esm(() => {
|
|
|
43854
44487
|
DIALECT5 = {
|
|
43855
44488
|
mysql: "MySQL",
|
|
43856
44489
|
mariadb: "MariaDB",
|
|
43857
|
-
postgresql: "Postgresql"
|
|
44490
|
+
postgresql: "Postgresql",
|
|
44491
|
+
sqlite: "Sqlite"
|
|
43858
44492
|
};
|
|
43859
44493
|
});
|
|
43860
44494
|
|
|
@@ -44986,12 +45620,14 @@ var init_not_in_nullable = __esm(() => {
|
|
|
44986
45620
|
"VARIANCE",
|
|
44987
45621
|
"VAR_POP",
|
|
44988
45622
|
"VAR_SAMP"
|
|
44989
|
-
])
|
|
45623
|
+
]),
|
|
45624
|
+
sqlite: new Set([...COMMON_NULL_ON_EMPTY_AGGREGATES, "GROUP_CONCAT", "STRING_AGG"])
|
|
44990
45625
|
};
|
|
44991
45626
|
NON_NULL_AGGREGATES = {
|
|
44992
45627
|
postgresql: new Set(["COUNT", "REGR_COUNT"]),
|
|
44993
45628
|
mysql: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
|
|
44994
|
-
mariadb: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"])
|
|
45629
|
+
mariadb: new Set(["COUNT", "BIT_AND", "BIT_OR", "BIT_XOR"]),
|
|
45630
|
+
sqlite: new Set(["COUNT", "TOTAL"])
|
|
44995
45631
|
};
|
|
44996
45632
|
notInNullableRule = {
|
|
44997
45633
|
name: "not-in-nullable",
|
|
@@ -45854,7 +46490,7 @@ __export(exports_snapshot, {
|
|
|
45854
46490
|
snapshotCommand: () => snapshotCommand
|
|
45855
46491
|
});
|
|
45856
46492
|
import { join as join39 } from "path";
|
|
45857
|
-
function
|
|
46493
|
+
function requireSqlConnection4(connection) {
|
|
45858
46494
|
if (!SQL_SYSTEMS6.includes(connection.system)) {
|
|
45859
46495
|
throw new Error(`snapshot currently supports SQL engines only, got: ${connection.system}`);
|
|
45860
46496
|
}
|
|
@@ -45902,7 +46538,7 @@ var init_snapshot = __esm(() => {
|
|
|
45902
46538
|
const snippets = await loadSnippets(dirs);
|
|
45903
46539
|
sql = resolveByName(snippets, query.slice(1), engine).query.sqlBody;
|
|
45904
46540
|
}
|
|
45905
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
46541
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection4(config.connection));
|
|
45906
46542
|
await adapter.connect();
|
|
45907
46543
|
try {
|
|
45908
46544
|
const blacklistManager = new BlacklistManager(config);
|
|
@@ -46138,7 +46774,7 @@ __export(exports_assert, {
|
|
|
46138
46774
|
assertCommand: () => assertCommand
|
|
46139
46775
|
});
|
|
46140
46776
|
import { basename as basename7 } from "path";
|
|
46141
|
-
function
|
|
46777
|
+
function requireSqlConnection5(connection) {
|
|
46142
46778
|
if (!SQL_SYSTEMS7.includes(connection.system)) {
|
|
46143
46779
|
throw new Error(`assert currently supports SQL engines only, got: ${connection.system}`);
|
|
46144
46780
|
}
|
|
@@ -46193,7 +46829,7 @@ var init_assert = __esm(() => {
|
|
|
46193
46829
|
console.error("Database not configured. Run: dbcli init");
|
|
46194
46830
|
process.exit(1);
|
|
46195
46831
|
}
|
|
46196
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
46832
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection5(config.connection));
|
|
46197
46833
|
await adapter.connect();
|
|
46198
46834
|
let auditRef = null;
|
|
46199
46835
|
let verdict2;
|
|
@@ -47578,7 +48214,7 @@ __export(exports_verify, {
|
|
|
47578
48214
|
verifyCommand: () => verifyCommand,
|
|
47579
48215
|
BUILTIN_VERIFY_SCENARIOS: () => BUILTIN_VERIFY_SCENARIOS
|
|
47580
48216
|
});
|
|
47581
|
-
function
|
|
48217
|
+
function requireSqlConnection6(connection, scenario2) {
|
|
47582
48218
|
if (!SQL_SYSTEMS8.includes(connection.system)) {
|
|
47583
48219
|
throw new Error(`verify ${scenario2} currently supports SQL engines only, got: ${connection.system}`);
|
|
47584
48220
|
}
|
|
@@ -48264,7 +48900,7 @@ async function executeScenario(def, options, command) {
|
|
|
48264
48900
|
console.error("Database not configured. Run: dbcli init");
|
|
48265
48901
|
process.exit(1);
|
|
48266
48902
|
}
|
|
48267
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
48903
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection6(config.connection, def.name));
|
|
48268
48904
|
await adapter.connect();
|
|
48269
48905
|
const runners = def.createRunners({ adapter, config, options, targetTable: input.table }, input);
|
|
48270
48906
|
if (!input.afterWrite) {
|
|
@@ -50139,7 +50775,7 @@ __export(exports_shell, {
|
|
|
50139
50775
|
import { createInterface as createInterface2 } from "readline";
|
|
50140
50776
|
import { join as join40 } from "path";
|
|
50141
50777
|
import { homedir as homedir3 } from "os";
|
|
50142
|
-
function
|
|
50778
|
+
function requireSqlConnection7(connection) {
|
|
50143
50779
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
50144
50780
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
50145
50781
|
}
|
|
@@ -50184,7 +50820,7 @@ async function runShell(options, configPath) {
|
|
|
50184
50820
|
const connectionOpts = config.connection;
|
|
50185
50821
|
const mongoInner = isMongoDB ? AdapterFactory.createMongoDBAdapter(config) : null;
|
|
50186
50822
|
const redisInner = isRedis ? AdapterFactory.createRedisAdapter(config) : null;
|
|
50187
|
-
const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(
|
|
50823
|
+
const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(requireSqlConnection7(connectionOpts));
|
|
50188
50824
|
try {
|
|
50189
50825
|
await adapter.connect();
|
|
50190
50826
|
} catch (error) {
|
|
@@ -50999,7 +51635,7 @@ __export(exports_migrate, {
|
|
|
50999
51635
|
runDDL: () => runDDL,
|
|
51000
51636
|
migrateCommand: () => migrateCommand
|
|
51001
51637
|
});
|
|
51002
|
-
function
|
|
51638
|
+
function requireSqlConnection8(connection) {
|
|
51003
51639
|
if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
|
|
51004
51640
|
throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
|
|
51005
51641
|
}
|
|
@@ -51023,7 +51659,7 @@ async function runDDL(operation, opts, command) {
|
|
|
51023
51659
|
console.error("\u6B64\u547D\u4EE4\u76EE\u524D\u4E0D\u652F\u63F4 Elasticsearch\uFF1B\u5982\u9700\u5EFA\u7ACB\u7D22\u5F15\u6216\u8ABF\u6574 mapping\uFF0C\u8ACB\u6539\u7528\u5916\u90E8\u5DE5\u5177");
|
|
51024
51660
|
process.exit(1);
|
|
51025
51661
|
}
|
|
51026
|
-
const adapter = AdapterFactory.createSqlAdapter(
|
|
51662
|
+
const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection8(config.connection));
|
|
51027
51663
|
const isDryRun = !opts.execute;
|
|
51028
51664
|
if (!isDryRun) {
|
|
51029
51665
|
await adapter.connect();
|
|
@@ -51265,17 +51901,23 @@ async function switchDefault(configPath, name, config, options = {}) {
|
|
|
51265
51901
|
function listConnectionsForDisplay(config) {
|
|
51266
51902
|
return Object.entries(config.connections).map(([name, conn]) => {
|
|
51267
51903
|
const marker = name === config.default ? "*" : " ";
|
|
51904
|
+
const environmentLabel = conn.environment ? ` [${conn.environment}]` : "";
|
|
51905
|
+
const file = conn.file;
|
|
51906
|
+
if (file !== undefined) {
|
|
51907
|
+
const rendered = typeof file === "object" ? `\${${file.$env}}` : file;
|
|
51908
|
+
return `${marker} ${name.padEnd(12)}${environmentLabel} ${conn.system.padEnd(12)} ${rendered}`;
|
|
51909
|
+
}
|
|
51268
51910
|
const host = typeof conn.host === "object" ? `\${${conn.host.$env}}` : conn.host;
|
|
51269
51911
|
const port = typeof conn.port === "object" ? `\${${conn.port.$env}}` : conn.port;
|
|
51270
51912
|
const db = typeof conn.database === "object" ? `\${${conn.database.$env}}` : conn.database;
|
|
51271
|
-
|
|
51272
|
-
return `${marker} ${name.padEnd(12)}${environment} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
|
|
51913
|
+
return `${marker} ${name.padEnd(12)}${environmentLabel} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
|
|
51273
51914
|
});
|
|
51274
51915
|
}
|
|
51275
51916
|
function listConnectionIdentities(config) {
|
|
51276
51917
|
return Object.entries(config.connections).map(([name, conn]) => {
|
|
51277
51918
|
const host = typeof conn.host === "string" && conn.host.length > 0 ? conn.host : null;
|
|
51278
51919
|
const database = typeof conn.database === "string" && conn.database.length > 0 ? conn.database : null;
|
|
51920
|
+
const file = conn.file;
|
|
51279
51921
|
return {
|
|
51280
51922
|
name,
|
|
51281
51923
|
environment: conn.environment ?? null,
|
|
@@ -51286,6 +51928,7 @@ function listConnectionIdentities(config) {
|
|
|
51286
51928
|
port: host !== null && typeof conn.port === "number" ? conn.port : null
|
|
51287
51929
|
},
|
|
51288
51930
|
database,
|
|
51931
|
+
...typeof file === "string" && { file },
|
|
51289
51932
|
isDefault: name === config.default
|
|
51290
51933
|
};
|
|
51291
51934
|
});
|
|
@@ -51386,7 +52029,7 @@ var init_config_v2_mutations = __esm(() => {
|
|
|
51386
52029
|
});
|
|
51387
52030
|
|
|
51388
52031
|
// src/core/env-file-writer.ts
|
|
51389
|
-
import { chmod as chmod2, mkdir as
|
|
52032
|
+
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
51390
52033
|
import { dirname as dirname16 } from "path";
|
|
51391
52034
|
function escapeForRegExp(value) {
|
|
51392
52035
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -51399,7 +52042,7 @@ async function upsertEnvVar(envPath, varName, value) {
|
|
|
51399
52042
|
const code = cause.code;
|
|
51400
52043
|
if (code !== "ENOENT")
|
|
51401
52044
|
throw cause;
|
|
51402
|
-
await
|
|
52045
|
+
await mkdir19(dirname16(envPath), { recursive: true });
|
|
51403
52046
|
}
|
|
51404
52047
|
const line = `${varName}="${value}"`;
|
|
51405
52048
|
const existing = new RegExp(`^${escapeForRegExp(varName)}=.*$`, "m");
|
|
@@ -51704,7 +52347,7 @@ var init_sql_metadata = __esm(() => {
|
|
|
51704
52347
|
});
|
|
51705
52348
|
|
|
51706
52349
|
// src/proxy/events.ts
|
|
51707
|
-
import { appendFile as appendFile2, mkdir as
|
|
52350
|
+
import { appendFile as appendFile2, mkdir as mkdir20, readFile as readFile10, stat as stat9 } from "fs/promises";
|
|
51708
52351
|
import { dirname as dirname17 } from "path";
|
|
51709
52352
|
function hasSql(e) {
|
|
51710
52353
|
return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
|
|
@@ -51744,7 +52387,7 @@ class EventWriter {
|
|
|
51744
52387
|
}
|
|
51745
52388
|
async writeInternal(event) {
|
|
51746
52389
|
if (!this.dirEnsured) {
|
|
51747
|
-
await
|
|
52390
|
+
await mkdir20(dirname17(this.path), { recursive: true });
|
|
51748
52391
|
this.dirEnsured = true;
|
|
51749
52392
|
}
|
|
51750
52393
|
if (!this.initialized) {
|
|
@@ -54156,7 +54799,7 @@ __export(exports_backfill, {
|
|
|
54156
54799
|
backfillCommand: () => backfillCommand
|
|
54157
54800
|
});
|
|
54158
54801
|
import { dirname as dirname18, resolve as resolve12 } from "path";
|
|
54159
|
-
import { mkdir as
|
|
54802
|
+
import { mkdir as mkdir21 } from "fs/promises";
|
|
54160
54803
|
function identityFor(config, name) {
|
|
54161
54804
|
const connection = config.connections[name];
|
|
54162
54805
|
if (!connection) {
|
|
@@ -54206,7 +54849,7 @@ var init_backfill = __esm(() => {
|
|
|
54206
54849
|
return;
|
|
54207
54850
|
}
|
|
54208
54851
|
const out = resolve12(options.out ?? `.dbcli/backfills/${artifact2.source.sha256.slice(0, 12)}.json`);
|
|
54209
|
-
await
|
|
54852
|
+
await mkdir21(dirname18(out), { recursive: true });
|
|
54210
54853
|
await Bun.write(out, JSON.stringify(artifact2, null, 2) + `
|
|
54211
54854
|
`);
|
|
54212
54855
|
console.log(JSON.stringify({ path: out, artifact: artifact2 }, null, 2));
|
|
@@ -54349,7 +54992,7 @@ var init_legacy2 = __esm(() => {
|
|
|
54349
54992
|
|
|
54350
54993
|
// src/core/evidence-pack/index.ts
|
|
54351
54994
|
import { createHash as createHash10, randomUUID as randomUUID6 } from "crypto";
|
|
54352
|
-
import { link as link3, lstat as lstat7, mkdir as
|
|
54995
|
+
import { link as link3, lstat as lstat7, mkdir as mkdir22, realpath as realpath4, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
|
|
54353
54996
|
import { basename as basename9, dirname as dirname19, relative as relative6, resolve as resolve13, sep as sep8 } from "path";
|
|
54354
54997
|
function isRecord6(value) {
|
|
54355
54998
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -54633,7 +55276,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
|
|
|
54633
55276
|
if (!isInside(workspace, existingAncestor2) && existingAncestor2 !== workspace) {
|
|
54634
55277
|
throw new EvidencePackValidationError("output path resolves outside the workspace");
|
|
54635
55278
|
}
|
|
54636
|
-
await
|
|
55279
|
+
await mkdir22(parent, { recursive: true });
|
|
54637
55280
|
const realParent = await realpath4(parent);
|
|
54638
55281
|
if (!isInside(workspace, realParent) && realParent !== workspace) {
|
|
54639
55282
|
throw new EvidencePackValidationError("output path resolves outside the workspace");
|
|
@@ -55278,9 +55921,9 @@ function assessImpact(input) {
|
|
|
55278
55921
|
...nodes.map((node) => node.reference),
|
|
55279
55922
|
...contractResult.metrics.map((metric) => `metric:${metric.name}`)
|
|
55280
55923
|
]);
|
|
55281
|
-
const
|
|
55282
|
-
findings.push(...
|
|
55283
|
-
if (
|
|
55924
|
+
const access2 = dataAccessFindings(change, references, input.dataAccess.value, input.blockedIdentifiers);
|
|
55925
|
+
findings.push(...access2.findings);
|
|
55926
|
+
if (access2.redacted)
|
|
55284
55927
|
addGap(gaps, "REDACTED_PROTECTED_SUBJECT");
|
|
55285
55928
|
}
|
|
55286
55929
|
}
|
|
@@ -56075,7 +56718,7 @@ __export(exports_impact, {
|
|
|
56075
56718
|
impactCommand: () => impactCommand
|
|
56076
56719
|
});
|
|
56077
56720
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
56078
|
-
import { link as link4, lstat as lstat9, mkdir as
|
|
56721
|
+
import { link as link4, lstat as lstat9, mkdir as mkdir23, realpath as realpath6, unlink as unlink9, writeFile as writeFile11 } from "fs/promises";
|
|
56079
56722
|
import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve15, sep as sep10 } from "path";
|
|
56080
56723
|
function collectOption3(value, previous) {
|
|
56081
56724
|
return [...previous, value];
|
|
@@ -56296,7 +56939,7 @@ async function resolveOutput(workspaceRoot, outputPath) {
|
|
|
56296
56939
|
const existingAncestor2 = await realExistingAncestor2(parent);
|
|
56297
56940
|
if (!isInside2(workspace, existingAncestor2) && existingAncestor2 !== workspace)
|
|
56298
56941
|
throw new Error("output path must stay inside the workspace");
|
|
56299
|
-
await
|
|
56942
|
+
await mkdir23(parent, { recursive: true });
|
|
56300
56943
|
const realParent = await realpath6(parent);
|
|
56301
56944
|
if (!isInside2(workspace, realParent) && realParent !== workspace)
|
|
56302
56945
|
throw new Error("output path must stay inside the workspace");
|
|
@@ -57166,7 +57809,7 @@ __export(exports_completion, {
|
|
|
57166
57809
|
});
|
|
57167
57810
|
import { join as join46 } from "path";
|
|
57168
57811
|
import { homedir as homedir4 } from "os";
|
|
57169
|
-
import { mkdir as
|
|
57812
|
+
import { mkdir as mkdir24 } from "fs/promises";
|
|
57170
57813
|
function resolveHome() {
|
|
57171
57814
|
return process.env.HOME ?? homedir4();
|
|
57172
57815
|
}
|
|
@@ -57439,7 +58082,7 @@ async function installCompletion(shell, script) {
|
|
|
57439
58082
|
const targetPath = getInstallPath2(shell);
|
|
57440
58083
|
if (shell === "fish") {
|
|
57441
58084
|
const dir = join46(resolveHome(), ".config", "fish", "completions");
|
|
57442
|
-
await
|
|
58085
|
+
await mkdir24(dir, { recursive: true });
|
|
57443
58086
|
await Bun.file(targetPath).write(script);
|
|
57444
58087
|
console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
|
|
57445
58088
|
return;
|