diff --git a/shortcuts/apps/apps_db_audit_list.go b/shortcuts/apps/apps_db_audit_list.go index ca03092e5..1b3545bf7 100644 --- a/shortcuts/apps/apps_db_audit_list.go +++ b/shortcuts/apps/apps_db_audit_list.go @@ -40,7 +40,7 @@ var AppsDBAuditList = common.Shortcut{ {Name: "until", Desc: "filter: event at or before; same formats as --since"}, {Name: "page-size", Type: "int", Default: "20", Desc: "page size"}, {Name: "page-token", Desc: "pagination cursor from previous response"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -145,7 +145,10 @@ func fetchExistingTables(rctx *common.RuntimeContext, appID, env string) (map[st existing := map[string]bool{} token := "" for { - params := map[string]interface{}{"env": env, "page_size": 100} + params := map[string]interface{}{"page_size": 100} + if env != "" { + params["env"] = env + } if token != "" { params["page_token"] = token } @@ -168,7 +171,11 @@ func fetchExistingTables(rctx *common.RuntimeContext, appID, env string) (map[st // fetchAuditEnabledTables 拉审计状态,返回当前已开启审计的表名集合(status 命令同源接口)。 func fetchAuditEnabledTables(rctx *common.RuntimeContext, appID, env string) (map[string]bool, error) { - data, err := rctx.CallAPITyped("GET", appAuditStatusPath(appID), map[string]interface{}{"env": env}, nil) + statusParams := map[string]interface{}{} + if env != "" { + statusParams["env"] = env + } + data, err := rctx.CallAPITyped("GET", appAuditStatusPath(appID), statusParams, nil) if err != nil { return nil, err } @@ -208,11 +215,10 @@ func auditListTables(rctx *common.RuntimeContext) []string { // buildAuditListParams 组装 audit_list 查询参数:env / tables(逗号拼接) / page_size 及可选 since/until/page_token。 func buildAuditListParams(rctx *common.RuntimeContext, tables []string) map[string]interface{} { - params := map[string]interface{}{ - "env": dbEnv(rctx), + params := dbEnvParams(rctx, map[string]interface{}{ "tables": strings.Join(tables, ","), "page_size": rctx.Int("page-size"), - } + }) addStr := func(flag, key string) { if v := strings.TrimSpace(rctx.Str(flag)); v != "" { params[key] = v diff --git a/shortcuts/apps/apps_db_audit_set.go b/shortcuts/apps/apps_db_audit_set.go index 93f0d4f7e..5833d3a86 100644 --- a/shortcuts/apps/apps_db_audit_set.go +++ b/shortcuts/apps/apps_db_audit_set.go @@ -35,7 +35,7 @@ var AppsDBAuditEnable = common.Shortcut{ {Name: "app-id", Desc: "Miaoda app id", Required: true}, {Name: "table", Desc: "table to enable audit for", Required: true}, {Name: "retention", Default: "7d", Enum: auditRetentions, Desc: "how long to keep audit logs"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -47,7 +47,7 @@ var AppsDBAuditEnable = common.Shortcut{ return common.NewDryRunAPI(). POST(appAuditSetPath(appID)). Desc("Enable table audit"). - Params(map[string]interface{}{"env": dbEnv(rctx)}). + Params(dbEnvParams(rctx, map[string]interface{}{})). Body(map[string]interface{}{"table": strings.TrimSpace(rctx.Str("table")), "enabled": true, "retention": rctx.Str("retention")}) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { @@ -60,7 +60,7 @@ var AppsDBAuditEnable = common.Shortcut{ stop := rctx.StartSpinner("Enabling audit logging for " + table) defer stop() data, err := rctx.CallAPITyped("POST", appAuditSetPath(appID), - map[string]interface{}{"env": dbEnv(rctx)}, + dbEnvParams(rctx, map[string]interface{}{}), map[string]interface{}{"table": table, "enabled": true, "retention": retention}) stop() if err != nil { @@ -96,7 +96,7 @@ var AppsDBAuditDisable = common.Shortcut{ Flags: append([]common.Flag{ {Name: "app-id", Desc: "Miaoda app id", Required: true}, {Name: "table", Desc: "table to disable audit for", Required: true}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -108,7 +108,7 @@ var AppsDBAuditDisable = common.Shortcut{ return common.NewDryRunAPI(). POST(appAuditSetPath(appID)). Desc("Disable table audit"). - Params(map[string]interface{}{"env": dbEnv(rctx)}). + Params(dbEnvParams(rctx, map[string]interface{}{})). Body(map[string]interface{}{"table": strings.TrimSpace(rctx.Str("table")), "enabled": false}) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { @@ -118,7 +118,7 @@ var AppsDBAuditDisable = common.Shortcut{ } table := strings.TrimSpace(rctx.Str("table")) data, err := rctx.CallAPITyped("POST", appAuditSetPath(appID), - map[string]interface{}{"env": dbEnv(rctx)}, + dbEnvParams(rctx, map[string]interface{}{}), map[string]interface{}{"table": table, "enabled": false}) if err != nil { return withAppsHint(err, dbAuditSetHint) diff --git a/shortcuts/apps/apps_db_audit_status.go b/shortcuts/apps/apps_db_audit_status.go index 341e11bdb..6221eea36 100644 --- a/shortcuts/apps/apps_db_audit_status.go +++ b/shortcuts/apps/apps_db_audit_status.go @@ -30,7 +30,7 @@ var AppsDBAuditStatus = common.Shortcut{ Flags: append([]common.Flag{ {Name: "app-id", Desc: "Miaoda app id", Required: true}, {Name: "table", Desc: "show status for a single table (default: all configured tables)"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -75,7 +75,7 @@ var AppsDBAuditStatus = common.Shortcut{ // buildAuditStatusParams 组装 audit_status 查询参数:env 及可选 table(单表查询)。 func buildAuditStatusParams(rctx *common.RuntimeContext) map[string]interface{} { - params := map[string]interface{}{"env": dbEnv(rctx)} + params := dbEnvParams(rctx, map[string]interface{}{}) if t := strings.TrimSpace(rctx.Str("table")); t != "" { params["table"] = t } diff --git a/shortcuts/apps/apps_db_changelog_list.go b/shortcuts/apps/apps_db_changelog_list.go index 052bbf9fe..d007b4b02 100644 --- a/shortcuts/apps/apps_db_changelog_list.go +++ b/shortcuts/apps/apps_db_changelog_list.go @@ -39,7 +39,7 @@ var AppsDBChangelogList = common.Shortcut{ {Name: "until", Desc: "filter: changed at or before; same formats as --since"}, {Name: "page-size", Type: "int", Default: "20", Desc: "page size"}, {Name: "page-token", Desc: "pagination cursor from previous response"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -77,10 +77,9 @@ var AppsDBChangelogList = common.Shortcut{ // buildChangelogParams 组装 changelog_list 查询参数:env / page_size 及可选 table/change_id/since/until/page_token。 func buildChangelogParams(rctx *common.RuntimeContext) map[string]interface{} { - params := map[string]interface{}{ - "env": dbEnv(rctx), + params := dbEnvParams(rctx, map[string]interface{}{ "page_size": rctx.Int("page-size"), - } + }) addStr := func(flag, key string) { if v := strings.TrimSpace(rctx.Str(flag)); v != "" { params[key] = v diff --git a/shortcuts/apps/apps_db_data_export.go b/shortcuts/apps/apps_db_data_export.go index 775406409..9ac45e689 100644 --- a/shortcuts/apps/apps_db_data_export.go +++ b/shortcuts/apps/apps_db_data_export.go @@ -47,7 +47,7 @@ var AppsDBDataExport = common.Shortcut{ {Name: "table", Desc: "source table", Required: true}, {Name: "output", Desc: "local output path; extension picks format .csv/.json/.sql (default: .csv)"}, {Name: "limit", Type: "int", Default: "5000", Desc: "max rows to export (1..5000)"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "source db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "source db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -75,10 +75,10 @@ var AppsDBDataExport = common.Shortcut{ return common.NewDryRunAPI(). GET(appDataExportPath(appID)). Desc("Export Miaoda app table data (raw bytes)"). - Params(map[string]interface{}{ - "env": dbEnv(rctx), "table": strings.TrimSpace(rctx.Str("table")), + Params(dbEnvParams(rctx, map[string]interface{}{ + "table": strings.TrimSpace(rctx.Str("table")), "format": format, "limit": rctx.Int("limit"), - }) + })) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { appID, err := requireAppID(rctx.Str("app-id")) @@ -95,15 +95,18 @@ var AppsDBDataExport = common.Shortcut{ // total 查询失败不阻断导出——回退到按导出文件内容数行。 total, totalErr := queryExportTotal(rctx, appID, dbEnv(rctx), table) + exportQuery := larkcore.QueryParams{ + "table": []string{table}, + "format": []string{format}, + "limit": []string{strconv.Itoa(rctx.Int("limit"))}, + } + if env := dbEnv(rctx); env != "" { + exportQuery["env"] = []string{env} + } resp, err := rctx.DoAPI(&larkcore.ApiReq{ - HttpMethod: http.MethodGet, - ApiPath: appDataExportPath(appID), - QueryParams: larkcore.QueryParams{ - "env": []string{dbEnv(rctx)}, - "table": []string{table}, - "format": []string{format}, - "limit": []string{strconv.Itoa(rctx.Int("limit"))}, - }, + HttpMethod: http.MethodGet, + ApiPath: appDataExportPath(appID), + QueryParams: exportQuery, }) if err != nil { return withAppsHint(errs.NewNetworkError(errs.SubtypeNetworkTransport, "export request failed").WithCause(err).WithRetryable(), dbDataExportHint) @@ -157,8 +160,11 @@ var AppsDBDataExport = common.Shortcut{ // queryExportTotal 调 GetAppTableRecordList(page_size=1)取 total(符合条件的记录总数)。 // 该接口与 +db-data-export 同为 spark:app:read scope,避免导出命令被迫升级到写权限。 func queryExportTotal(rctx *common.RuntimeContext, appID, env, table string) (int, error) { - raw, err := rctx.CallAPITyped("GET", appTableRecordsPath(appID, table), - map[string]interface{}{"env": env, "page_size": 1}, nil) + params := map[string]interface{}{"page_size": 1} + if env != "" { + params["env"] = env + } + raw, err := rctx.CallAPITyped("GET", appTableRecordsPath(appID, table), params, nil) if err != nil { return 0, err } diff --git a/shortcuts/apps/apps_db_data_import.go b/shortcuts/apps/apps_db_data_import.go index d3266eeb0..ac94906a0 100644 --- a/shortcuts/apps/apps_db_data_import.go +++ b/shortcuts/apps/apps_db_data_import.go @@ -44,7 +44,7 @@ var AppsDBDataImport = common.Shortcut{ {Name: "app-id", Desc: "Miaoda app id", Required: true}, {Name: "file", Desc: "local data file (.csv/.json), relative to cwd", Required: true}, {Name: "table", Desc: "target table (default: file name without extension)"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -76,7 +76,7 @@ var AppsDBDataImport = common.Shortcut{ return common.NewDryRunAPI(). POST(appDataImportPath(appID)). Desc("Import data file into Miaoda app table (multipart upload)"). - Params(map[string]interface{}{"env": dbEnv(rctx), "table": importTableName(rctx)}). + Params(dbEnvParams(rctx, map[string]interface{}{"table": importTableName(rctx)})). Body(map[string]interface{}{"file_name": fileName, "file": ""}) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { @@ -100,10 +100,14 @@ var AppsDBDataImport = common.Shortcut{ fd.AddField("file_name", fileName) fd.AddFile("file", bytes.NewReader(content)) + importQuery := larkcore.QueryParams{"table": []string{table}} + if env := dbEnv(rctx); env != "" { + importQuery["env"] = []string{env} + } resp, err := rctx.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: appDataImportPath(appID), - QueryParams: larkcore.QueryParams{"env": []string{dbEnv(rctx)}, "table": []string{table}}, + QueryParams: importQuery, Body: fd, }, larkcore.WithFileUpload()) if err != nil { diff --git a/shortcuts/apps/apps_db_execute.go b/shortcuts/apps/apps_db_execute.go index 290e470fe..ceae294ca 100644 --- a/shortcuts/apps/apps_db_execute.go +++ b/shortcuts/apps/apps_db_execute.go @@ -66,7 +66,7 @@ var AppsDBExecute = common.Shortcut{ {Name: "sql", Desc: "SQL text; use - to read stdin. Mutually exclusive with --file", Input: []string{common.Stdin}}, {Name: "file", Desc: "path to a .sql file (relative to cwd). Mutually exclusive with --sql"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -291,10 +291,9 @@ func parseErrorSentinel(data string) (int, string) { // // CLI 永远走 DBA 模式,原子性由用户在 SQL 内显式 BEGIN/COMMIT 控制;不暴露 transactional flag 给用户。 func buildDBSQLParams(rctx *common.RuntimeContext) map[string]interface{} { - return map[string]interface{}{ - "env": dbEnv(rctx), + return dbEnvParams(rctx, map[string]interface{}{ "transactional": false, - } + }) } // resolveExecuteSQL 返回要执行的 SQL,在用时(DryRun/Execute)现读,使 --file 的内容 diff --git a/shortcuts/apps/apps_db_quota_get.go b/shortcuts/apps/apps_db_quota_get.go index f5f1563be..ab7abca33 100644 --- a/shortcuts/apps/apps_db_quota_get.go +++ b/shortcuts/apps/apps_db_quota_get.go @@ -29,7 +29,7 @@ var AppsDBQuotaGet = common.Shortcut{ HasFormat: true, Flags: append([]common.Flag{ {Name: "app-id", Desc: "Miaoda app id", Required: true}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -41,14 +41,14 @@ var AppsDBQuotaGet = common.Shortcut{ return common.NewDryRunAPI(). GET(appDbQuotaPath(appID)). Desc("Get Miaoda app database storage usage"). - Params(map[string]interface{}{"env": dbEnv(rctx)}) + Params(dbEnvParams(rctx, map[string]interface{}{})) }, Execute: func(ctx context.Context, rctx *common.RuntimeContext) error { appID, err := requireAppID(rctx.Str("app-id")) if err != nil { return err } - data, err := rctx.CallAPITyped("GET", appDbQuotaPath(appID), map[string]interface{}{"env": dbEnv(rctx)}, nil) + data, err := rctx.CallAPITyped("GET", appDbQuotaPath(appID), dbEnvParams(rctx, map[string]interface{}{}), nil) if err != nil { return withAppsHint(err, appIDListHint) } diff --git a/shortcuts/apps/apps_db_table_get.go b/shortcuts/apps/apps_db_table_get.go index 5aff1852d..91f6a8841 100644 --- a/shortcuts/apps/apps_db_table_get.go +++ b/shortcuts/apps/apps_db_table_get.go @@ -37,7 +37,7 @@ var AppsDBTableGet = common.Shortcut{ Flags: append([]common.Flag{ {Name: "app-id", Desc: "app id", Required: true}, {Name: "table", Desc: "table name", Required: true}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -80,7 +80,7 @@ var AppsDBTableGet = common.Shortcut{ // CLI 检测 rctx.Format == "pretty" 时给 server 带 format=ddl,要求返 CREATE 语句文本; // 其他 format(含默认 json)不传该参数,让 server 返默认结构化字段。 func buildDBTableGetParams(rctx *common.RuntimeContext) map[string]interface{} { - params := map[string]interface{}{"env": dbEnv(rctx)} + params := dbEnvParams(rctx, map[string]interface{}{}) if rctx.Format == "pretty" { params["format"] = "ddl" } diff --git a/shortcuts/apps/apps_db_table_list.go b/shortcuts/apps/apps_db_table_list.go index b24c04d83..e5bcded2e 100644 --- a/shortcuts/apps/apps_db_table_list.go +++ b/shortcuts/apps/apps_db_table_list.go @@ -42,7 +42,7 @@ var AppsDBTableList = common.Shortcut{ {Name: "app-id", Desc: "app id", Required: true}, {Name: "page-size", Type: "int", Default: "20", Desc: "page size"}, {Name: "page-token", Desc: "pagination cursor from previous response"}, - }, dbEnvFlags("dev", []string{"dev", "online"}, "target db environment (default dev; use online for the online environment, or for an app whose DB is not multi-env)")...), + }, dbEnvFlags("", []string{"dev", "online"}, "target db environment; leave unset to auto-select (multi-env app uses dev, single-env uses online), or pass dev/online")...), Validate: func(ctx context.Context, rctx *common.RuntimeContext) error { if _, err := requireAppID(rctx.Str("app-id")); err != nil { return err @@ -110,10 +110,9 @@ func projectTableListItems(raw interface{}) []dbTableListItem { } func buildDBTableListParams(rctx *common.RuntimeContext) map[string]interface{} { - params := map[string]interface{}{ - "env": dbEnv(rctx), + params := dbEnvParams(rctx, map[string]interface{}{ "page_size": rctx.Int("page-size"), - } + }) if token := strings.TrimSpace(rctx.Str("page-token")); token != "" { params["page_token"] = token } diff --git a/shortcuts/apps/db_common.go b/shortcuts/apps/db_common.go index 982c9bee2..3dc6b30ee 100644 --- a/shortcuts/apps/db_common.go +++ b/shortcuts/apps/db_common.go @@ -34,6 +34,16 @@ func dbEnv(rctx *common.RuntimeContext) string { return rctx.Str("environment") } +// dbEnvParams 把 env 并入 params:仅当显式指定了环境(非空)才带 env 键;未指定(空)时 +// 省略该键,由服务端按应用多环境状态自动选分支(多环境→dev,单环境→online)。与家族对 +// 空可选参数的 omit-empty 约定一致——不发空串,wire 上真正不带 env。原样返回同一个 map 便于链式。 +func dbEnvParams(rctx *common.RuntimeContext, params map[string]interface{}) map[string]interface{} { + if env := dbEnv(rctx); env != "" { + params["env"] = env + } + return params +} + // rejectLegacyEnvFlag 在 Validate 阶段拦截已移除的 --env:显式传了就报清晰的 validation 错,指向 --environment。 func rejectLegacyEnvFlag(rctx *common.RuntimeContext) error { if rctx.Changed("env") { diff --git a/skills/lark-apps/references/lark-apps-db-execute.md b/skills/lark-apps/references/lark-apps-db-execute.md index c0baf815f..c2af72f5d 100644 --- a/skills/lark-apps/references/lark-apps-db-execute.md +++ b/skills/lark-apps/references/lark-apps-db-execute.md @@ -11,7 +11,7 @@ - 必填:`--app-id`,以及 `--sql` / `--file` 二选一(互斥)。 - `--sql`:内联 SQL 文本;传 `-` 时从 stdin 读。绝对路径文件经 stdin 传入:`--sql - < `(shell 解析路径,CLI 仅接收内容)。 - `--file`:`.sql` 文件路径,需为工作目录内的相对路径(如 `--file ./migration.sql`);绝对路径、或经 `..`/符号链接越出工作目录的路径会被拒绝。文件不在工作目录内时,改用 `--sql - < <文件路径>` 经 stdin 传入。 -- `--environment` 枚举:`dev` / `online`,**默认 `dev`**;操作线上库、或**未开启多环境的应用(其数据库在 `online`,没有 dev 分支)**时显式 `--environment online`。旧名 `--env` 已**移除**:传入会报 validation 错(提示改用 `--environment`),一律用 `--environment`。 +- `--environment` 枚举:`dev` / `online`,**不传则由服务端按应用是否开启多环境自动选择(多环境→`dev`,未开启多环境→`online`)**;要固定环境就显式传 `--environment dev|online`。**未开启多环境的应用显式传 `--environment dev` 会报错(无 dev 分支)——这类应用不传 `--environment`(走 `online`)或显式 `--environment online`**。旧名 `--env` 已**移除**:传入会报 validation 错(提示改用 `--environment`),一律用 `--environment`。 - risk 是 `high-risk-write`(SQL 可含 DML/DDL):任何执行都需 `--yes`,否则返回 `confirmation_required` / exit 10。`--dry-run` 预览不需要 `--yes`。 - **不会自动为你包事务,事务边界需自己在 SQL 里控制**:多语句默认逐条独立提交,中间某条失败时前序语句已生效、不会回滚;若需要「要么全部成功、要么全部回滚」的原子性,请在 SQL 内显式写 `BEGIN … COMMIT`(详见下「Agent 规则」)。 diff --git a/skills/lark-apps/references/lark-apps-db.md b/skills/lark-apps/references/lark-apps-db.md index e903b2f03..88f6e40c1 100644 --- a/skills/lark-apps/references/lark-apps-db.md +++ b/skills/lark-apps/references/lark-apps-db.md @@ -28,7 +28,7 @@ ## 约定(先读) -- **环境 `--environment dev|online`(所有 db 命令统一默认 `dev`)**:看表、看结构、数据导入导出、变更追溯、审计、配额都按环境区分,写操作建议先在 `dev` 验。**注意:只有开启了多环境(`+db-env-create`)的应用才有 `dev` 分支;未开启多环境的应用其数据库在 `online`——对这类应用必须显式 `--environment online`,否则默认的 `dev` 分支不存在、会报错**。旧名 `--env` 已**移除**:传入会报 validation 错(提示改用 `--environment`),一律用 `--environment`。`+db-env-diff`/`+db-env-migrate` 是「dev→online 发布」语义、`+db-recovery-*` 作用于当前库,二者**没有** `--environment`。 +- **环境 `--environment dev|online`(可省略)**:看表、看结构、数据导入导出、变更追溯、审计、配额都按环境区分。省略 `--environment` 时 CLI 不带该参数、由服务端按应用形态自动选分支——多环境应用走 `dev`、未开多环境的走 `online`;要固定环境就显式传。唯一会报错的组合:对未开多环境的应用显式传 `--environment dev`(无 `dev` 分支)。写操作建议先在 `dev` 验(仅多环境应用有 `dev`)。旧名 `--env` 已**移除**:传入会报 validation 错(提示改用 `--environment`),一律用 `--environment`。`+db-env-diff`/`+db-env-migrate` 是「dev→online 发布」语义、`+db-recovery-*` 作用于当前库,二者**没有** `--environment`。 - **本地文件 / `--output` 用工作目录内相对路径**:导入 `--file ./orders.csv`、导出 `--output ./out.csv`;绝对路径、或经 `..`/符号链接越出工作目录的 `--output` 会被拒(validation / exit 2)。路径在别处先 `cd` 过去或改成相对路径。 - **高危操作必须带 `--yes`**:`+db-env-create`、`+db-data-import`、`+db-env-migrate`、`+db-recovery-apply` 缺省会被确认关卡拦下;动手前先用对应的预览命令或 `--dry-run` 看清影响。 - **时间参数按口语自然传**(`--since`/`--until`/`--target`),格式见末尾。 @@ -154,7 +154,7 @@ lark-cli apps +db-quota-get --app-id app_xxx --environment dev ## Agent 规则 -- 用户说「本地 / 开发库 / 调试库」优先 `--environment dev`,线上排查用 `--environment online`;数据面写操作(导入 / 审计开关)默认先在 `dev` 验再动 `online`。 +- 用户说「本地 / 开发库 / 调试库」优先 `--environment dev`,线上排查用 `--environment online`;数据面写操作(导入 / 审计开关)建议先在 `dev` 验再动 `online`。**注意省略 `--environment` 时写操作会落到服务端选中的分支——单环境应用即 `online`(生产)**:不确定应用是否多环境时,写操作显式传 `--environment`;显式 `dev` 在单环境应用上会安全报错(无 dev 分支),正好当「是否多环境」的探针用。 - 看表用 `+db-table-list`,看结构用 `+db-table-get`(要建表语句加 `--format pretty`);`+db-env-create` 仅用于存量单库拆多环境,新建的 full_stack 应用一般不需要。 - 四个高危命令(`+db-env-create`、`+db-data-import`、`+db-env-migrate`、`+db-recovery-apply`)动手前先看清影响再带 `--yes`:发布 / 恢复先跑对应预览 `+db-env-diff` / `+db-recovery-diff`,导入无预览命令、可先 `--dry-run` 看请求或先在 `--environment dev` 验;不要静默追加 `--yes`,遇 confirmation_required(exit 10)按 lark-shared 协议向用户确认不可逆风险后再补 `--yes` 重试。 - 导入 / 导出的本地路径用工作目录内相对路径;超大表导出会被行数 / 体积上限拒,改用 `+db-execute` 分批。 diff --git a/tests/cli_e2e/apps/apps_db_execute_dryrun_test.go b/tests/cli_e2e/apps/apps_db_execute_dryrun_test.go index adbcc778b..f801ebd29 100644 --- a/tests/cli_e2e/apps/apps_db_execute_dryrun_test.go +++ b/tests/cli_e2e/apps/apps_db_execute_dryrun_test.go @@ -15,11 +15,11 @@ import ( ) // TestAppsDBExecuteDryRun pins +db-execute 复用存量 URL,CLI 永远走 DBA 模式 -// (?transactional=false),sql body 由 --sql 透传,默认 env=dev。 +// (?transactional=false),sql body 由 --sql 透传,默认不传 env(空值,由服务端按 workspace 定分支)。 func TestAppsDBExecuteDryRun(t *testing.T) { setAppsDryRunEnv(t) - t.Run("DefaultEnvIsDevAndTransactionalFalse", func(t *testing.T) { + t.Run("DefaultEnvUnsetAndTransactionalFalse", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) @@ -37,8 +37,8 @@ func TestAppsDBExecuteDryRun(t *testing.T) { "CLI is DBA mode → must send transactional=false in query") assert.False(t, gjson.Get(result.Stdout, "api.0.body.transactional").Exists(), "transactional should be in query, not body") - assert.Equal(t, "dev", gjson.Get(result.Stdout, "api.0.params.env").String(), - "default env must be dev (not production)") + assert.False(t, gjson.Get(result.Stdout, "api.0.params.env").Exists(), + "default: no --environment → env key must be omitted (server picks workspace default branch)") }) t.Run("OnlineEnvSwitch", func(t *testing.T) { diff --git a/tests/cli_e2e/apps/apps_db_table_list_dryrun_test.go b/tests/cli_e2e/apps/apps_db_table_list_dryrun_test.go index b99d99b6f..28e1e2eae 100644 --- a/tests/cli_e2e/apps/apps_db_table_list_dryrun_test.go +++ b/tests/cli_e2e/apps/apps_db_table_list_dryrun_test.go @@ -19,7 +19,7 @@ import ( func TestAppsDBTableListDryRun(t *testing.T) { setAppsDryRunEnv(t) - t.Run("DefaultsToDevAndPageSize20", func(t *testing.T) { + t.Run("DefaultsToNoEnvAndPageSize20", func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) t.Cleanup(cancel) @@ -32,7 +32,8 @@ func TestAppsDBTableListDryRun(t *testing.T) { assert.Equal(t, "GET", gjson.Get(result.Stdout, "api.0.method").String()) assert.Equal(t, "/open-apis/spark/v1/apps/app_x/tables", gjson.Get(result.Stdout, "api.0.url").String()) - assert.Equal(t, "dev", gjson.Get(result.Stdout, "api.0.params.env").String()) + assert.False(t, gjson.Get(result.Stdout, "api.0.params.env").Exists(), + "default: no --environment → env key must be omitted (server picks workspace default branch)") assert.Equal(t, "20", gjson.Get(result.Stdout, "api.0.params.page_size").String()) assert.False(t, gjson.Get(result.Stdout, "api.0.params.page_token").Exists(), "empty page_token must be omitted")