diff --git a/Dockerfile b/Dockerfile index 38464d2..ac8f348 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -ARG CHEN_BASE_IMAGE=jumpserver/chen-base:20261104_063401 +ARG CHEN_BASE_IMAGE=jumpserver/chen-base:20261104_090447 FROM ${CHEN_BASE_IMAGE} AS stage-build ARG TARGETARCH ARG VERSION=dev diff --git a/Makefile b/Makefile index 91f099f..f3faa05 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ GO ?= go VERSION ?= dev PLATFORM ?= linux/$(shell $(GO) env GOARCH) APT_MIRROR ?= https://deb.debian.org -BASE_VERSION ?= 20261104_063401 +BASE_VERSION ?= 20261104_090447 CHEN_BASE_IMAGE ?= jumpserver/chen-base:$(BASE_VERSION) CHEN_IMAGE ?= jumpserver/chen:$(VERSION)-ce CHEN_EE_IMAGE ?= jumpserver/chen:$(VERSION)-ee diff --git a/README.md b/README.md index c9474fd..1436679 100644 --- a/README.md +++ b/README.md @@ -1,87 +1,69 @@ # Chen -Chen is JumpServer's database workspace. The backend is Go; the Luna frontend -uses Monaco. Queries, catalog browsing, completion and cancellation use HTTP, -with one SSE stream per session for execution and control updates. Wisp connects -the managed service to JumpServer authentication, permissions, command approval, -session control, audit and heartbeats. +Chen is JumpServer's database workspace. It provides SQL queries, schema browsing, +result editing and data import/export through the Luna frontend. The Go backend +integrates with JumpServer through Wisp for authentication, permissions and audit. -The workspace includes queries, inspection, draft-based grid editing, CSV import -and table/index DDL previews. Writes use the same JumpServer policy and audit -path. See [write workflows and limits](docs/writes.md). +## Build -## Run locally - -Go 1.26.3 and a C compiler are required: PostgreSQL parsing uses `libpg_query` -through CGO. Node.js/pnpm are only needed when building Luna. +Use the Go version specified in [go.mod](go.mod) and a C compiler. PostgreSQL SQL +parsing requires CGO. Build the frontend separately in the Luna repository. ```sh make build make test vet ``` -For managed mode, start Core and supply its bootstrap credential through your -private environment or a private Wisp configuration file. Never commit those -values. Existing Wisp registration is retained in `data/keys`. +To build and run the container image: ```sh -CORE_HOST=http://127.0.0.1:8080 ./start.sh managed +make image-ee VERSION=dev PLATFORM=linux/arm64 +make run ``` -`start.sh` builds Go and pinned Wisp v0.2.15. Managed mode listens on -`127.0.0.1:8082`; the HTTP API is under `/chen/api/v1/`. The startup script keeps -Wisp alive while Chen closes sessions and drains its audit outbox. Neither Java -nor Maven is part of the build or startup path. +Linux amd64 and arm64 builds include all supported database drivers, including +the pure Go Db2 driver. -## Run without Core +## Run with JumpServer -```sh -# In the adjacent Luna checkout: -pnpm install --frozen-lockfile -pnpm generate:chen +Start Core and configure the bootstrap credential through the environment or a +private Wisp configuration file, then run: -# In Chen: -mkdir -p data -cp conf/standalone_backends_example.yaml data/standalone_backends.yml -chmod 600 data/standalone_backends.yml -# Edit this private file with the database connection details. -make single BACKENDS=data/standalone_backends.yml WEB_DIR=../luna/.output/public +```sh +CORE_HOST=http://127.0.0.1:8080 ./start.sh managed ``` -Open `http://127.0.0.1:8083/luna/chen/?selector=local-pg`, using a selector from -the configuration. Single mode does not contact Core or Wisp. Keep its listener -behind your intended access boundary; a selector is a routing name, not a secret. -See [single mode](docs/single.md). +Managed mode listens on `127.0.0.1:8082`. Keep registration keys in `data/keys` +and audit state in `data/go` persistent; container data is under `/opt/chen/data`. +The API is served at `/chen/api/v1/` with SSE streaming. Use `chen-go healthcheck` +for liveness and `chen-go readycheck` for readiness. -## Build and run images +## Run without JumpServer -The formal build is `Dockerfile-base` → `Dockerfile` → `Dockerfile-ee`. -The default image contains Go and Wisp and retains the existing JumpServer -`chen:8082` endpoint and `/opt/chen/data` volume. +Build Luna's standalone frontend using its declared Node.js and pnpm versions: ```sh -make image-ee VERSION=dev PLATFORM=linux/arm64 -make run -# Or start in the background after providing Core configuration: -make dev-up +pnpm --dir ../luna install --frozen-lockfile +pnpm --dir ../luna generate:chen +mkdir -p data +cp conf/standalone_backends_example.yaml data/standalone_backends.yml +chmod 600 data/standalone_backends.yml ``` -The standard image supports Linux amd64 and arm64, including Db2. Db2 uses -`github.com/go-db2/go-db2`, a pure Go DRDA driver pinned in `go.mod`. No IBM CLI, -Db2-specific build tag, native runtime or separate image variant is required. -Chen still needs CGO for PostgreSQL SQL parsing. +Edit the configuration with your database connection details, then start Chen: -See [build, release and integration acceptance](docs/build.md) and -[database compatibility](docs/database-compatibility.md) for limits and evidence. +```sh +make single BACKENDS=data/standalone_backends.yml WEB_DIR=../luna/.output/public +``` -## Development and integration +Open `http://127.0.0.1:8083/luna/chen/?selector=local-pg`, using a selector from +the [configuration](conf/standalone_backends_example.yaml). Single mode has no +Core authentication; keep access limited to the intended users. The selector +identifies a connection and is not a password. -- [Go backend architecture and APIs](docs/go-v1.md) -- [JumpServer integration, approvals and validation boundaries](internal/jumpserver/README.md) -- [SQL analysis and completion](internal/sqlservice/README.md) -- [Build and integration acceptance](docs/build.md) +## Release packages -Luna is a separate repository. Build its corresponding revision into docker-web; -rebuilding Chen does not update the browser's static assets. The normal managed -route, single route, web application and Electron integration must all select -the same backend generation before consuming a one-use connection token. +`make package` extracts the built image into `build/chen--linux-.tar.gz`. +The archive includes binaries, configuration examples, licenses and this README. +On a matching Linux system, extract it and run `./start.sh managed`, or +`./start.sh single --backends /path/to/backends.yml --web-dir /path/to/luna`. diff --git a/cmd/chen-go/main.go b/cmd/chen-go/main.go index f03d41a..bb12335 100644 --- a/cmd/chen-go/main.go +++ b/cmd/chen-go/main.go @@ -96,10 +96,6 @@ func parseOptions(args []string) (options, error) { // Only the selected mode initializes its provider. Single mode never constructs // a Wisp client or starts the managed audit/control background processes. -func configuredProvider(o options) (jumpserver.Provider, func(), error) { - return configuredProviderContext(context.Background(), o) -} - func configuredProviderContext(startupCtx context.Context, o options) (jumpserver.Provider, func(), error) { if o.single { config, err := standalone.Load(o.backends, standalone.LoadOptions{AllowInsecurePermissions: o.insecureBackends}) diff --git a/cmd/chen-go/main_test.go b/cmd/chen-go/main_test.go index 7f6f830..95dfee8 100644 --- a/cmd/chen-go/main_test.go +++ b/cmd/chen-go/main_test.go @@ -57,7 +57,7 @@ func TestSingleProviderDoesNotInitializeWisp(t *testing.T) { if err := os.WriteFile(path, []byte(config), 0600); err != nil { t.Fatal(err) } - p, stop, err := configuredProvider(options{single: true, backends: path, state: dir, wisp: "intentionally-invalid-address", ca: "/does-not-exist.pem"}) + p, stop, err := configuredProviderContext(context.Background(), options{single: true, backends: path, state: dir, wisp: "intentionally-invalid-address", ca: "/does-not-exist.pem"}) if err != nil { t.Fatalf("single mode depended on Wisp configuration: %v", err) } diff --git a/docker-compose.yml b/docker-compose.yml index 8f4c98a..e11dd3d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: context: . dockerfile: Dockerfile args: - CHEN_BASE_IMAGE: ${CHEN_BASE_IMAGE:-jumpserver/chen-base:20261104_063401} + CHEN_BASE_IMAGE: ${CHEN_BASE_IMAGE:-jumpserver/chen-base:20261104_090447} VERSION: ${VERSION:-dev} environment: CORE_HOST: ${CORE_HOST:-http://host.docker.internal:8080} diff --git a/docs/build.md b/docs/build.md deleted file mode 100644 index b586927..0000000 --- a/docs/build.md +++ /dev/null @@ -1,331 +0,0 @@ -# Go build and JumpServer integration - -## Build chain - -The primary `Dockerfile-base` contains Go 1.26.3, its target-architecture C -compiler, and Wisp v0.2.15. `Dockerfile` compiles Chen with CGO and copies only the -runtime executable and Wisp into Debian bookworm-slim. -`Dockerfile-ee` retains the existing thin CE extension (ping/telnet). Java, Maven, -Node.js and the source tree do not enter the final runtime image. - -BuildKit builds CGO on the target architecture. Use native amd64/arm64 builders -for release or QEMU for local acceptance. Wisp alone is pure Go and can be -cross-built on the build platform. Do not use `CGO_ENABLED=0` for Chen, or copy its -glibc-linked binary into Alpine. - -```sh -make image-ee VERSION=dev PLATFORM=linux/arm64 -make image-ee VERSION=dev PLATFORM=linux/amd64 -``` - -The default base is pinned consistently in `Dockerfile`, `Makefile` and -`docker-compose.yml`. `make image` builds it locally before using it. Set -`CHEN_BASE_IMAGE` to use another exact reference or digest. - -Like Magnus, the Build and Push Base Image workflow runs on base-related changes -in PRs targeting `dev` or `v*`, or manually with an existing source branch. One job -publishes `jumpserver/chen-base:` for amd64/arm64. An existing -tag is rejected. Only after the multi-architecture build and push succeed does it commit the three default references back to the source -PR/branch. The workflow recognizes that exact generated three-file delta and -skips its own rebuild (PR path filters include the whole PR diff). Application CI -still validates the updated defaults. - -Manual builds of `dev`, version branches or other protected branches create a -`pr@@build_chen_base_` branch and PR instead of pushing directly -to the protected branch. A source branch that advances during the build is not -overwritten; rebuild its latest revision. Fork PRs only build both architectures, with -no registry login, publication or writeback. Like Magnus, publication requires -`PRIVATE_TOKEN` with repository push/PR permissions so the existing generic PR -handler and subsequent CI can run. A missing token fails before the image is -published. There is no `GITHUB_TOKEN` fallback: bot-token pushes would not trigger -the application checks on the updated commit. - -The writeback helper can preview its exact three-file diff without mutation: - -```sh -GITHUB_REPOSITORY=jumpserver/chen python3 scripts/update-base-image.py 20261026_120000 \ - --source-branch dev --source-sha "$(git rev-parse HEAD)" --dry-run -``` - -The helper requires a clean checkout at the built SHA and uses read-only GitHub -branch metadata even in dry-run mode. It never stages unrelated files or forces -a branch update. - -Source builds and release packaging use the primary `Dockerfile` and -`docker-compose.yml` entry points. - -## Db2 in the standard build - -Db2 uses the pinned `github.com/go-db2/go-db2` pure Go DRDA driver and is included -in the standard amd64 and arm64 builds. The base, application image and native -release archive need no IBM CLI, `ibmdb` tag, `WITH_DB2` switch or `-db2` variant. -The existing standard image names and `make build/image/image-ee/package` -commands apply to every supported database. - -TLS uses the same Core/single-mode CA, server name and client certificate -configuration as the other database drivers. Context cancellation interrupts the -connection; it does not prove the server rolled back a dispatched statement. -Chen reports an unconfirmed dispatched outcome as unknown and invalidates the -console. Estimated plans still use Db2 LUW's own EXPLAIN tables and optimizer. -Historical IBM CLI results do not establish acceptance of the new driver. -The pinned runtime source and LOB framing corrections are recorded in -`third_party/go-db2/CHEN_PATCH.md`; database compatibility records distinguish -the new driver checks from earlier native-driver acceptance. - -## Runtime and existing installations - -The runtime uses UID/GID 10001 and port 8082. Docker creates new named volumes -with the image directory ownership. For an existing bind-mounted data directory, -arrange write access for UID 10001 before switching images; keep `data/keys` and -`data/go` private and persistent. Do not discard registration or queued audits -as part of migration. The Go audit state is separate from legacy Java state. - -The default entrypoint supervises Wisp and Chen as siblings. It sends TERM to -Chen, waits up to 40 seconds for HTTP/session cleanup and audit drain, then stops -Wisp. Compose allows 55 seconds. If either process exits unexpectedly, the other -is stopped and the container fails. Wisp v0.2.15's own subprocess supervisor uses -SIGKILL; it is deliberately not used to own the Go process. - -The Wisp build applies the reviewed Chen patch to a private copy of v0.2.15 and -records `v0.2.15+chen.1` in `wisp.version`. It removes the startup Config print, -which includes BootstrapToken, and acknowledges command uploads only after the -configured audit storage has saved them. An upload failure leaves Chen's durable -outbox available for retry; stopping Wisp must not discard an acknowledged -in-memory command queue. Delivery remains at least once: if persistence succeeds -but its acknowledgement is lost, a retry can produce a duplicate audit record. -The original module cache and local Wisp source checkout remain unchanged. -The initial `-chen1` base tag and subsequent timestamp releases contain this -patch; `wisp.version` records the contract independently of the image tag. - -`chen-go --version` prints the build version, OS/architecture and Wisp module -version. `chen-go healthcheck` checks its own `/healthy` listener without ambient -proxies or redirects. This is liveness, not proof of Core registration or an -open authorized database session. Managed acceptance must establish those -separately. HTTP and query logs must not contain connection credentials. - -### Managed startup and readiness - -Previously the entrypoint started both processes and Chen immediately opened -HTTP: `grpc.NewClient` only configured a client. `/healthy` could succeed while -Wisp was still validating its access key or registering with Core. - -The sibling process layout is retained, but startup now follows: - -```text -entrypoint starts Wisp and Chen - Wisp retries Core registration/key validation, loads terminal configuration - Chen waits for a successful Wisp GetPublicSetting RPC (including Status.Ok/data) - Chen initializes its persistent outbox and starts audit/control workers - Chen opens HTTP; /ready additionally checks local initialization and control state -``` - -`CHEN_STARTUP_TIMEOUT=120s` (or `--startup-timeout 120s`) bounds Chen's managed -startup wait. Each RPC has a two-second deadline, retries pause one second, and -each failed attempt logs a fixed code and attempt number. No RPC response, -Core URL, token or credential is logged by these probes. Wisp has its own bounded -registration retry policy and can fail before this deadline; increasing Chen's -timeout does not extend Wisp's retry policy. The supervisor propagates an early -Wisp exit, stops a waiting Chen, and treats an unexpected zero exit as failure. -SIGTERM cancels the wait promptly. After startup the existing shutdown order is -unchanged: Chen HTTP shutdown (10s), session cleanup (15s), provider drain (up to -12s), then Wisp; the supervisor's limits remain 40s/10s and Compose allows 55s. -The signal context used for startup does not cancel the running audit provider -before sessions have closed. - -No separate Core `/api/health/` gate is needed. That endpoint returns HTTP 200 -even when its JSON `status`, `db_status` or `redis_status` is false. A bare HTTP -200 would therefore not be a substitute for initialization. - -| Check | Meaning | -| --- | --- | -| `GET /healthy`, `chen-go healthcheck` | HTTP liveness, 200 with `{"status":"ok"}`; no external probe. | -| `GET /ready`, `chen-go readycheck` | 200 with `{"status":"ready"}`, or 503 with `{"status":"not_ready"}`. Local export storage must initialize and shutdown must not have begun. Managed mode also requires the local task worker to be available and a successful read-only Wisp/Core public-settings RPC. | -| `chen-go wisp-healthcheck` | One bounded Wisp public-settings RPC using the same address/TLS flags; opens no journal. Used for the separate Wisp Compose startup dependency. | - -Before managed initialization finishes, HTTP is not listening, so both HTTP -checks fail to connect; neither reports healthy/ready. After listening, a lost -dependency changes readiness to 503 without changing liveness, stopping sessions -or initiating a restart. Existing task-stream protection still applies. A -successful probe proves one Core settings read, **not** Core WebSocket health, -all database/Redis dependencies, permission validity or a database session. -Wisp v0.2.15 exposes no authoritative upstream WebSocket health signal. The local -stream flag is not an upstream handshake acknowledgement. No health request -consumes a one-time connection token. Single/standalone mode does not initialize -Wisp or depend on any Core check or managed startup timeout. - -Managed providers start fail-closed: `Open` refuses token exchange before the -control worker runs, as well as during a known control-channel outage. There is -no "not yet monitored" bypass. A recovered control stream restores readiness -without clearing independent administrator locks. - -Each `/ready` request makes one Wisp RPC and one Core settings GET; `/healthy` -makes neither. Use a modest readiness cadence (for example 15s), not a browser -polling loop. The two-second RPC deadline bounds Chen's response, but pinned -Wisp's SDK call does not inherit that context and may continue up to its own -30s HTTP timeout. No cache or background health state machine is introduced; -high-frequency probes would require a separate measured load/timeout change. - -The CE Dockerfile already defines the 15s/5s/60s/3-retry liveness HEALTHCHECK; -EE inherits it from CE. The base is a build image and needs no service check. -These intervals are unchanged. Installer's `chen-go healthcheck` branch remains -compatible; the `/chen/healthy` fallback is only for legacy Java images and must -not be selected for Go images. Keep readiness separate from restart decisions, -including in any external auto-heal tooling. Docker health status alone does -not restart a container; `restart: unless-stopped` acts on process exit. - -### Verify the deployed image and effective healthcheck - -Run these after updating/recreating Chen with the intended Installer/Compose -configuration. Do not delete the data volume or registration keys. A mutable -tag on the host may refer to a different image from the running container: - -```sh -docker image inspect jumpserver/chen:dev-ce \ - --format 'id={{.Id}} digests={{json .RepoDigests}} health={{json .Config.Healthcheck}}' -docker inspect jms_chen \ - --format 'tag={{.Config.Image}} id={{.Image}} health={{json .Config.Healthcheck}} state={{json .State.Health}} restarts={{.RestartCount}}' -image_id=$(docker inspect --format '{{.Image}}' jms_chen) -docker image inspect "$image_id" \ - --format 'id={{.Id}} labels={{json .Config.Labels}} user={{.Config.User}} health={{json .Config.Healthcheck}}' -docker exec jms_chen chen-go --version -docker exec jms_chen cat /usr/local/bin/wisp.version -docker exec jms_chen id -docker exec jms_chen chen-go healthcheck -docker exec jms_chen chen-go readycheck -``` - -In the deployment directory, use the **same Compose files and environment** as -Installer to inspect `docker compose config` and compare its `chen.healthcheck` -with the container output above. Avoid sharing the full config: environment -entries may include credentials. A container override can provide a working -check even when the selected image has none. If the image itself has `null`, -record the immutable image ID/digest and build version before attributing this -to a Dockerfile change. The 2026-11-02 supplied logs establish a roughly 22s -initialization gap followed by recovery, not persistent failure or a restart. - -## Separate Wisp during development - -```sh -make image -make run COMPOSE_FILES='-f docker-compose.yml -f docker-compose.wisp-dev.yml' -``` - -The override uses the same pinned image and exposes Wisp only inside the Compose -network. Remote plaintext gRPC is explicitly selected only on that private -network. For other deployments use `--wisp-ca` and `--wisp-server-name`. -Both services mount the same data volume at `/opt/chen/data`. Wisp reads replay -paths supplied by Go, so the replay directory must have the same absolute path -and file visibility in both processes. The default is -`/opt/chen/data/go/outbox/replays`. Separate containers must not mount this volume -at different locations. - -For Wisp source development, additionally apply -`docker-compose.wisp-source.yml` and set `WISP_SOURCE` to the source checkout. -Native `./start.sh managed` also accepts `WISP_SOURCE`; absent that setting it -builds the pinned module. There is no dependency on a sibling Wisp checkout or a -branch-derived registry tag. A Wisp configuration file must leave -`EXECUTE_PROGRAM` empty when using sibling-process mode. - -## Full JumpServer wiring - -1. Start matching Core, PostgreSQL, Redis and Core workers. Configure terminal - registration and the bootstrap credential privately. The Chen Compose project - does not create or reconfigure an existing Core installation. -2. Start managed Chen/Wisp; verify Core terminal registration and a live task - control stream. Retain the data volume across restarts. Core's `auto` - registration policy has a startup window; start a newly registered Wisp - within that window, and resolve registration-name conflicts in the intended - Core environment. Reuse the valid persisted component key on later starts. - A listening Chen health endpoint alone does not prove registration succeeded; - do not permanently open component registration to work around a failed start. -3. Build the corresponding Luna checkout, then include its generated `/opt/luna` - output in docker-web with Lina. docker-web's default Bake contexts point to - remote `dev`; override them with the intended local/source revision when - accepting unpublished changes. A new Chen image alone does not update Luna. -4. Route `/chen/api/v1/` to `chen:8082` as normal HTTP. Keep SSE response buffering - off and allow its long-lived read stream. Do not force WebSocket upgrade for - these requests. Core's separate WebSocket endpoints remain unchanged. -5. Use one public origin for Core, Luna and Chen; authenticate through the real - asset launch flow. Never retry a consumed connection token against a second - backend generation. Exercise the web and Electron connector selection. - -## Repeatable acceptance - -```sh -make test vet -./scripts/test-entrypoint.sh -make dev-config -make dev-config COMPOSE_FILES='-f docker-compose.yml -f docker-compose.wisp-dev.yml' -make image-ee VERSION=ci PLATFORM=linux/arm64 -./scripts/smoke-image.sh jumpserver/chen:ci-ce linux/arm64 0 -./scripts/smoke-image.sh jumpserver/chen:ci-ee linux/arm64 0 -``` - -The smoke script creates only its own network and disposable PostgreSQL/container -volumes, copies a private configuration with UID 10001, and removes those -resources on exit. It tests the actual non-root image: selector authentication, -SELECT, table/alias/JOIN completion, Unicode large values and exact bigint, -NULL/empty distinction, cancellation, local audit state and graceful TERM. It -also exports 10,001 synthetic rows as CSV and XLSX, validates exact Unicode and -integer values, verifies formula handling and NULL/empty distinction, and checks -that native POST download tickets are single use. The export files are written -by UID 10001 under the same persistent state path as the deployed service. -The retired synchronous CSV/JSON export URLs must return 404, so they cannot -bypass export-job auditing and permission checks. -The script also asserts CE/EE image and effective container HEALTHCHECK metadata, -checks `readycheck` in single mode, runs fake-child supervisor faults in the -runtime image, and verifies a real packaged Wisp/Chen startup against an -unreachable Core fails within its budget without becoming healthy. This negative -test uses a disposable volume and a closed loopback port, not an existing Core. -Go tests use a real gRPC transport with controlled service responses to cover -delayed Wisp, an initially failing upstream read followed by recovery, hung RPCs, -startup timeout/cancellation, and readiness loss/recovery with stable liveness. -These tests do not replace the real managed fixture below. - -`python3 scripts/test-managed-startup.py --chen data/bin/chen-go --wisp data/bin/wisp` -runs the actual native binaries and entrypoint against a disposable partial Core -stub with synthetic keys. Build them with `make build` and `./scripts/build-wisp.sh`. -It checks an initially refused Core connection followed by recovery, Chen's -short startup deadline, Wisp key-validation retry exhaustion, and graceful TERM. -Wisp v0.2.15 attempts registration/validation up to ten times with five-second -pauses: fast failures exhaust in about 50s, while request timeouts can take longer. -Chen's independent default budget is 120s. Increasing it does not extend Wisp's -policy. The image smoke similarly checks registration exhaustion with no stored -key. The stub deliberately rejects WebSocket upgrades: these are startup and -probe tests, not full Core authorization, database-session or audit acceptance. -Repeat for amd64 and pass `1` for the Db2 variant; capability presence plus `ldd` -checks verify packaging, while a real Db2 connection is a separate gate. - -The opt-in real database matrix uses an explicitly supplied private backend file: - -```sh -CHEN_READONLY_BACKENDS=/absolute/private/backends.yml \ - go test ./internal/engine -run TestReadonlyDatabaseMatrix -count=1 -v -# Db2 is included by default; no native client or build tag is required. -``` - -Managed acceptance is independently reproducible against an isolated Core/Wisp -fixture, including approval, control, permissions and audit: - -```sh -CHEN_MANAGED_FIXTURE=/absolute/private/fixture.json \ - go test -race ./internal/engine -run TestManagedLive -count=1 -v -``` - -Fixtures are not committed. A local/mock/HTTP pass is not evidence of a gateway, -production database, or packaged image pass. Record versions and architectures -for each actual run. The CI workflow builds and smoke-tests CE/EE on native amd64 -and arm64, with Db2 included in both. Tagged native releases include the standard -amd64/arm64 archives. Live external database and Core checks need -explicit private fixtures and are not silently substituted with mocks. - -## Native releases - -`make package CHEN_IMAGE=... VERSION=... PLATFORM=linux/amd64` exports the exact -built image into `build/chen--linux-.tar.gz` with SHA-256. The archive -uses standard version and architecture naming for every database driver. The archive -contains its binary, Wisp, license, configuration example, -and a relocatable start wrapper. It requires a glibc environment compatible with -Debian bookworm, plus the documented platform libraries. Run `./start.sh single ---backends ... --web-dir ...` or `./start.sh managed` after unpacking. Luna static -assets are built and supplied separately. diff --git a/docs/database-compatibility.md b/docs/database-compatibility.md deleted file mode 100644 index 61784bc..0000000 --- a/docs/database-compatibility.md +++ /dev/null @@ -1,139 +0,0 @@ -# Go 数据库工作区:兼容性与验收 - -## 2026-11-04:Db2 驱动切换 - -Db2 改用固定提交的 `github.com/go-db2/go-db2` 纯 Go DRDA 驱动,标准 -amd64/arm64 构建均包含它。IBM CLI、`ibmdb` 编译标签和 `-db2` 镜像变体已移除。 -TLS 直接接受 Core/single 配置提供的 CA、服务器名与客户端证书;无需原生证书目录。 -固定提交的运行时源码保存在 `third_party/go-db2`,包含混合 LOB、空 LOB 和前导零二进制数据的协议修复,详见其 `CHEN_PATCH.md`。 - -取消通过中断连接结束本地等待;仅收到原操作的服务器取消 SQLSTATE 时才确认取消, -已发送而未确认的操作保持 unknown,控制台失效后需显式重建,不自动重放。 -事务所有权由连接适配器记录,结果类型、精确数值、LOB、多结果集和保存点须对新驱动 -重新验收。估算计划仍由 Db2 LUW 服务端生成,沿用 EXPLAIN 表读取和计划图归一化。 - -本次在独立 Db2 LUW 11.5.8 测试实例验证了精确 DECIMAL、中文/emoji、混合 CLOB/DBCLOB/BLOB、NULL/空值、前导零二进制、多查询块读取、参数化读写、commit/rollback 可见性、保存点、HTTP 结构化估算计划及辅助记录回滚、context 中断与连接失效。测试仅使用常量、系统目录和准确清理的独立临时测试表。 -Linux amd64/arm64 的 Db2 驱动均通过 `CGO_ENABLED=0` 编译;完整镜像、TLS 实库、其他 Db2 版本及真实 Core/Wisp 组合尚未逐项验收。 - -下文 IBM CLI 的实测结果属于旧驱动历史证据,不代表纯 Go 驱动已经通过相同验收。 - - -2026-10-26:旧版 Chen 的九类数据库入口已接入 Go 后端与 Luna Monaco,并完成本轮真实实例回归。 -本页前期记录覆盖只读工作区;随后已加入与 Java dev 对齐的网格写入、CSV 导入和结构修改,当前功能及限制见[写入说明](writes.md)。下列只读历史记录不代表写入测试。 - -## dev 迁移后的增量验收 - -在更新到本机最新 `dev` 并迁移 Go/Luna 后,10 个连接(本地隔离 PostgreSQL 加下表九个远程实例)的读取矩阵再次通过;目录详情另验证 Oracle/达梦原生 DDL 与 PostgreSQL 列权限过滤。完整 Luna 通过 Core 的真实登录、资产树及 Go 工作区入口;托管策略与录像验收见 [Go V1](go-v1.md)。 - -CSV/XLSX 完整导出不使用普通查询的 10,000 行/16 MiB 缓存窗口。真实 PostgreSQL 分别完成 12,000 行、约 20.6 MB 数据导出,并对默认 1,000,000 行和 128 MiB 上限实测明确失败、禁止部分文件下载及原连接恢复。格式约束和下载策略见 [导出说明](exports.md)。 - -以下时间、版本与浏览器细节保留为前一轮兼容性证据;不能将单机验证等同于每种数据库的全部托管功能或每个镜像架构都已通过。 - -### v5.1.1 能力恢复(2026-11-03) - -- 恢复权限管理、序列和管理 DDL;Oracle/SQL Server 的过程与控制块保留完整原文,并额外校验内部语句 ACL。达梦和 OceanBase Oracle 的兼容过程复用 Oracle 边界解析;不能证明边界的厂商过程语法仍明确拒绝。 -- 一般约束页恢复 PRIMARY KEY、FOREIGN KEY、UNIQUE、CHECK。MySQL/MariaDB、PostgreSQL、Oracle、SQL Server、达梦、Db2、OceanBase 两种租户分别使用各自目录;ClickHouse 沿用非关系约束边界。定义只展示数据库能提供且账号可见的内容。 -- Oracle 活动用户事务内的网格保存恢复保存点暂存,避免驱动隐式提交外层事务;Db2 和 ClickHouse 接入复制 SQL,ClickHouse 只生成不依赖唯一身份的 INSERT。 -- 表和视图数据页接入分页、计数及完整导出;函数、子查询及视图的重新执行须显式确认。MariaDB/OceanBase MySQL 按产品身份、实际 SQL mode 和目录能力校验,不再靠少数版本号白名单排除其他版本。 -- 恢复 PostgreSQL 显式 prefer/allow 连接模式,以及常用 Java ACL 前后查找、反向引用和字面引用。不能编译的模式或匹配超时明确失败。 - -本轮新增回归覆盖 10 种方言标识的复杂查询/筛选与边界拒绝、Oracle 保存点及 ACL 内层语句校验。隔离 PostgreSQL 16 的真实 HTTP 测试通过 10,037 行视图的分页、计数、完整 CSV 下载及函数/子查询筛选,也确认列授权不会暴露隐藏列的约束定义;现有达梦和 PostgreSQL 实例通过只读目录及一般约束检查。MySQL 配置缺少 database,MariaDB/Oracle/SQL Server 当前连接不可用;这些结果不能记为通过。Db2 仍需要 Linux amd64 的 IBM CLI 构建,OceanBase Oracle 未取得租户实测;本轮未把单元测试等同于全部数据库实库验收。 - -## 真实实例结果 - -下列九项已使用用户提供的测试实例,通过 `TestReadonlyDatabaseMatrix`。 -测试只读取目录元数据,使用 `WHERE 1=0` 校验带引号对象引用,通过纯常量 SELECT 生成大字段。 -未创建、修改或删除数据库对象,未读取业务表记录,未使用 Keycloak 专用数据库。 - -| 数据库 | 连接与目录 | 表名/字段/别名/JOIN | 大字段、精确值及审计 | 限制 | -| --- | --- | --- | --- | --- | -| MySQL | 通过 | 通过 | 通过 | TiDB 语法与 MySQL 并非完全相同 | -| MariaDB | 通过 | 通过 | 通过 | 扩展 SELECT 语法可交服务器验证 | -| PostgreSQL | 通过 | 通过 | 通过 | 本地语法版本为 PostgreSQL 17 | -| SQL Server | 通过 | 通过 | 通过 | 支持 `[]` 引用和 TOP 投影补全 | -| Oracle | 通过 | 通过 | 通过 | 目录支持 Oracle 12c+;database 为 service name | -| Dameng | 通过 | 通过 | 通过 | 驱动不能可靠停止运行中的查询,主动取消已禁用;TLS 厂商证书配置尚未接入 | -| Db2 | 通过 | 通过 | 通过 | 需 IBM CLI;暂不支持主动取消已运行查询;TLS 配置尚未接入 | -| ClickHouse | 通过 | 通过 | 通过 | 支持 native/HTTP;数组内 UInt64 保留为精确字符串 | -| OceanBase MySQL 租户 | 通过 | 通过 | 通过 | 使用官方 OceanBase Go 驱动 | -| OceanBase Oracle 租户 | 已实现,未实测 | 已实现及单测 | 未实测 | 提供的 CE 环境无 Oracle 租户,不以 MySQL 测试替代 | - -共同验收包括:身份对应的对象树、字段类型、schema 限定表名、别名字段、JOIN 同名字段限定、零行 SELECT、约 150–350 KiB 长文本(含中文/emoji)、超过 JavaScript 安全整数范围的数值、JSON 文本、NULL/空串、二进制十六进制(ClickHouse 测数组),以及真实 standalone 审计落盘。 -Oracle 将空字符串视为 NULL,工作区保留数据库本身语义。 - -Db2 另通过 DBCLOB 中文/emoji、ASCII BLOB、精确 DECIMAL、CLI 原生超时及严格 CGO 指针检查回归。CLI `QueryTimeout=1` 的计数查询约 1.3 秒返回 SQLSTATE 57014,随后连接可复用;服务默认 CLI QueryTimeout 为 60 秒。主动取消能力仍明确为 false,不能把超时等同于随时可取消。 - -本轮九库矩阵38.44秒通过,矩阵与取消组合测试55.23秒通过;Go带IBM CLI的全包race及常规vet通过。Luna 95项单元测试、类型检查、lint和静态构建通过。Monaco补全、大字段和审批/暂停交互另有浏览器fixture覆盖;该fixture使用模拟HTTP/SSE,不替代真实Core/Wisp验证。 - -取消实测分开记录: - -| 数据库 / 路径 | 本轮证据 | -| --- | --- | -| MySQL、MariaDB、PostgreSQL、ClickHouse native、OceanBase MySQL | 真实HTTP取消,确认服务器查询终止、审计记录、原物理连接可继续查询 | -| SQL Server | 驱动层WAITFOR取消55ms并复用原连接;直接QueryContext/ExecContext的attention确认可判定取消,Rows.Err的context错误不足以确认 | -| ClickHouse超时、会话关闭 | 精确query_id从服务器消失且审计落盘;原请求失去响应,终态保守为unknown | -| Dameng | 取消后等待当前驱动调用安全返回再丢弃连接,运行中主动取消能力关闭;超时和管理员关闭保持 unknown,不假定服务器写入被回滚 | -| Db2 | 主动取消不支持;既有CLI原生超时独立验证 | -| Oracle | 测试账号无DBMS_LOCK.SLEEP权限,未验证运行中取消;没有用高负载业务查询代替 | - -Oracle与Dameng新增原生空BLOB/CLOB、NULL LOB、非空二进制、中文emoji NCLOB回归。Db2另通过3MB Unicode DBCLOB、空值和精确DECIMAL回归。 - -浏览器额外复核 Oracle 对象树/别名/JOIN/CLOB 分段,SQL Server 方括号补全接受后执行零行查询,Db2 大文本与尾部查找;本轮更新后的本地PG页面再次验收表名与JOIN别名字段补全、取消后原连接可用、475 KiB长文本第二片段尾部查找、JSON大整数精度和二进制Hex/ASCII;页面保留纯常量JOIN样例。九库通用路径由 HTTP 实库 matrix 覆盖,未声称对九库全部 UI 功能分别穷举。 - -## 本轮实测修复 - -- ClickHouse为每次执行分配服务器生成的query_id,以独立控制连接精确KILL,修复客户端取消但服务器继续运行;自动超时和管理关闭也使用该路径。 -- Oracle改为`LOB FETCH=post`并修复go-ora空LOB转换,区别空BLOB/CLOB与NULL。固定版本源代码和许可证位于`third_party/go-ora`,仅一处转换逻辑补丁,说明见`CHEN_PATCH.md`。 -- SQL Server 连接配置使用驱动完整默认值,修复初始化 panic。 -- Dameng 按其实际 DSN 解析规则处理密码,修复错误的 URL 编码。 -- Dameng 的取消 watcher 会与语句/事务状态并发修改,已在统一 connector 边界屏蔽驱动内部异步关闭,覆盖目录、预编译、事务、Ping 与结果集。调用前检查取消,调用中取消后等待安全返回并丢弃连接;这不是服务器主动取消。固定 dial 10 秒、每次读取 120 秒、每次写入 15 秒,不能把每次 I/O 上限当成整个查询的硬上限。驱动自身 `socketTimeout` 在该版本仅影响默认 TCP 拨号,因此由独立 socket 包装实际设置 deadline;通讯失败按结果未知处理,不自动重试 SQL。 -- Dameng 固定版本源码另修复全局错误模板、过滤器配置读取及 DH 固定参数初始化的跨连接竞态,参见 `third_party/dm/CHEN_PATCH.md`。真实实例回归覆盖三连接并发读取超时、预编译 deadline、取消后的结果集清理,以及持有行锁时的超时和管理员会话关闭;后两者验证服务器最终仍可完成写入,而 API 与持久审计保持 unknown。 -- Oracle 改用有针对性的目录查询,保持 3 秒目录超时,并隐藏 Oracle 维护的系统模式。 -- IBM Go 驱动的 CGO 缓冲、空字符串、DBCLOB Unicode、精确小数读取已做固定版本源代码补丁;保留许可证和上游 SHA,详见 `third_party/go_ibm_db/CHEN_PATCH.md`。未关闭 CGO 检查,也未分发 IBM CLI 二进制到源码仓库。 -- 补全修复 SQL Server TOP CTE、Oracle MINUS、NQ 与多字节 q 引号边界;引用名称保持正确的大小写与转义。 -- 大字段查看器保留分段、完整复制、JSON 精确格式化、二进制 Hex/ASCII;查找不再自动把整个长单词填入输入框。 - -## 验证入口 - -```sh -# 常规构建不包含需要本机 IBM CLI 的 Db2 驱动。 -go test -race ./... -go vet ./... - -# 标准构建默认包含 Db2,无需 IBM CLI 或编译标签。 -go build -o data/bin/chen-go ./cmd/chen-go - -# 私有 YAML 必须是 0600;不得把密码放命令行或测试报告。 -CHEN_READONLY_BACKENDS=/private/path/backends.yml \ -go test ./internal/engine -run '^TestReadonly(DatabaseMatrix|DriverCancellation|HTTPCancellation|ClickHouseServerCancellation|ClickHouseAutomaticCancellation)$' -v -count=1 -``` - -可用 `CHEN_READONLY_SELECTOR` 只验收一个 selector。测试日志只记录检查项和结果,不输出连接凭据或业务记录。 -默认 `Dockerfile` 和标准镜像均内置纯 Go Db2 驱动,amd64/arm64 使用相同构建入口。 - -## 解析与传输边界 - -MySQL/PostgreSQL 使用现有原生语法解析器;新增方言的执行路径使用有界语句扫描器,支持常见读写、结构修改和事务语句,检查引号、注释、括号、原文边界和含糊的批处理关键词,由真实服务器验证厂商语法。它不是完整 ANTLR AST,也不是数据库权限的替代品。新方言补全使用共享容错作用域与独立目录查询,不执行或 PREPARE 用户正在输入的 SQL。 - -认证、连接、目录、补全、提交和结果均走 HTTP;执行状态沿用每会话一条 SSE,没有新增 WebSocket。取消能力按实际驱动返回,未知执行结果不会自动重放。 - -当前是指定测试版本、账号和 single 模式的验收结果;不等于所有版本、TLS 配置或真实 Core/Wisp 托管部署均已验收。未在本次九类范围内的新数据源仍需独立适配。 - -## 本次本机体验入口 - -服务监听 `http://127.0.0.1:8083`,私有测试配置不进入仓库。以下 selector 仅属于本次本机环境: - -| 数据库 | 路径 | -| --- | --- | -| 本地 PostgreSQL | `/luna/chen/?selector=local-pg` | -| 测试 MySQL | `/luna/chen/?selector=qa-mysql` | -| 测试 MariaDB | `/luna/chen/?selector=qa-mariadb` | -| 测试 PostgreSQL | `/luna/chen/?selector=qa-pg` | -| 测试 SQL Server | `/luna/chen/?selector=qa-sqlserver` | -| 测试 Oracle | `/luna/chen/?selector=qa-oracle` | -| 测试 Dameng | `/luna/chen/?selector=qa-dameng` | -| 测试 Db2 | `/luna/chen/?selector=qa-db2` | -| 测试 ClickHouse | `/luna/chen/?selector=qa-clickhouse` | -| 测试 OceanBase MySQL | `/luna/chen/?selector=qa-oceanbase` | - -依赖核对补充:本机共享 Go 缓存的 `aws-sdk-go v1.44.306`、`golex v1.1.0`、`mathutil v1.6.0` 存在历史修改,整体 `go mod verify` 因此未通过;三者均未出现在本次服务二进制的实际依赖中。未改动用户共享缓存;隔离缓存全量重下载未完成,不能记录为校验通过。 diff --git a/docs/exports.md b/docs/exports.md deleted file mode 100644 index eebabc2..0000000 --- a/docs/exports.md +++ /dev/null @@ -1,35 +0,0 @@ -# Read-only result exports - -The workspace offers **cached result** export and **full query** export as separate choices. Cached export serializes the retained, already masked result and carries its `truncated` flag. It never reruns SQL. Full export requires explicit confirmation of a frozen SQL snapshot and reruns exactly one read query on the original console, preserving its database/schema, temporary tables and serial execution order. It is a new database read, so data may have changed since the visible result was produced. - -Managed sessions require Core's `can_download` permission at creation, during generation, when issuing a download ticket, and while streaming the file. Full export uses the same command ACL, warning confirmation, review ticket, masking, cancellation and audit path as interactive execution. Confirmation and review flags are bound to the immutable SQL and stable request ID. A failed confirmation request holds no artifact quota. Single mode enables download for its locally configured account; upload remains disabled. - -CSV and XLSX are written one row at a time to private temporary files. XLSX uses standard SpreadsheetML ZIP parts and inline text cells, without a shared-string table or an in-memory workbook. Numeric strings keep their precision; formula-looking values are text. The browser submits a native download form and Electron streams the response to its selected local file. Neither path loads the complete artifact into a JavaScript Blob. - -## Limits and representation - -- At most 1,000,000 data rows, 128 MiB of JSON-encoded raw records, and 128 MiB of generated file per job. Every limit fails explicitly and removes the partial file. -- XLSX allows at most 16,384 columns and 32,767 UTF-16 units in one cell. A value exceeding the cell limit fails with `export_cell_limit`; CSV preserves larger values within the job and 16 MiB per-value materialization budgets. Files are never silently truncated or split into misleading partial workbooks. -- Full export has a 10-minute execution/queue budget; waiting for command review pauses that budget. Cached export has a 2-minute generation budget. Driver cancellation capability remains authoritative: DM and Db2 do not advertise active query cancellation. -- Four artifacts or running jobs per server, retained for 15 minutes after generation. Failed jobs immediately release capacity. Deleting a completed export frees capacity early. A session close removes its artifacts. -- CSV prefixes formula-looking strings with an apostrophe. SQL NULL is `\N`; literal strings beginning with a backslash receive an extra leading backslash. Empty strings remain empty. XLSX represents SQL NULL as an absent cell and empty strings as explicit inline string cells. Precise numbers are text cells rather than rounded spreadsheet numbers. - -The root export directory and per-instance directories are mode `0700`; files and lock files are `0600`. Each server holds an OS file lock on its private instance directory. Startup removes abandoned instance directories after acquiring their lock, while preserving other live instances. Normal shutdown removes the instance directory. Older flat temporary exports are removed once older than 30 minutes. Unsupported process-lock platforms fail export storage initialization rather than sharing unprotected temporary files. - -## HTTP contract - -- `POST /chen/api/v1/results/{rid}/exports`: `{request_id, format: "csv" | "xlsx"}` creates a cached export. -- `POST /chen/api/v1/consoles/{cid}/exports`: `{sql, request_id, document_version, format, confirm_warnings?, submit_review?}` returns an execution with `export_id`. -- `GET /chen/api/v1/exports/{id}` and the existing session SSE `export.updated` expose status, rows, raw bytes, limits, expiry and readiness. Progress is emitted at most every 250 ms; completion is immediate. Only a finished file with successful audit is ready for download. -- `POST /chen/api/v1/exports/{id}/cancel` uses the existing execution cancellation contract; cached writing can always be canceled. `DELETE /chen/api/v1/exports/{id}` removes a finished artifact. -- `POST /chen/api/v1/exports/{id}/download-ticket` returns a one-use, 60-second ticket, a relative download endpoint and native form fields. Submit `application/x-www-form-urlencoded` to `POST /chen/api/v1/downloads`. The ticket is sent in the request body, never in the URL or referrer. No SQL runs when downloading. Choose an Electron save path before issuing the ticket. - -No background export creates business tables or uses database-side filesystem export commands. - -## Verification - -`go test -race ./internal/engine -run 'Test(FullExport|Export|CachedExport)'` covers generated 12,000-row / greater-than-16-MiB datasets, streaming CSV/XLSX decoding, exact numeric strings, Unicode, formula protection, NULL/empty strings, limits, policy gates, cancellation, one-use downloads, a session-close/audit race, and crash cleanup with another live instance present. Independent openpyxl decoding also verifies the synthetic XLSX artifact. - -With the private read-only backend fixture, `CHEN_READONLY_BACKENDS=/absolute/private/backends.yml go test ./internal/engine -run TestReadonlyPostgresExportJobs -v -count=1` exercises actual PostgreSQL constant queries, native HTTP file downloads, production row/byte thresholds and original-console recovery. It does not read application rows or write database objects. - -Spreadsheet limits and inline-string structure follow [Microsoft's Excel limits](https://support.microsoft.com/en-us/excel/excel-specifications-and-limits) and [SpreadsheetML cell documentation](https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cell). diff --git a/docs/go-v1.md b/docs/go-v1.md deleted file mode 100644 index 0de04e7..0000000 --- a/docs/go-v1.md +++ /dev/null @@ -1,138 +0,0 @@ -# Chen Go V1:数据库查询工作区 - -CSV/XLSX 后台导出:[流程、限制与验证](exports.md)。数据编辑、CSV 导入及结构修改:[写入流程](writes.md)。 - -本版本在 Go 服务与 Luna Monaco 工作区之间实现认证、连接、SQL执行、取消、语义补全、审计的闭环。本次扩展覆盖旧版数据库入口,具体驱动、构建条件和实测范围见[数据库兼容性](database-compatibility.md)。协议路由发生在交换一次性连接Token之前,失败后不会自动把同一Token转交另一个后端。 - -无需 Core/Wisp 的本机运行方式见 [single 模式](single.md):配置文件提供数据库信息,通过 URL 的 `selector` 直接打开工作区。 - -## 启动与路由 - -需要Go 1.26.3、C编译器(`pg_query_go`依赖CGO)、现有Core/Wisp。Wisp接口固定为`v0.2.15`;线上版本需完成部署验收。 - -```sh -GOTOOLCHAIN=go1.26.3 go build -o /tmp/chen-go ./cmd/chen-go -/tmp/chen-go --wisp-address 127.0.0.1:9090 --state-dir ./data/go -``` - -默认只监听`127.0.0.1:8083`。本机Wisp使用明文gRPC;远端Wisp需要`--wisp-ca /path/ca.pem`,或在受保护网络显式设置`--wisp-insecure`。数据库TLS配置来自Core,不能由浏览器覆盖。PostgreSQL支持disable、require、verify-ca、verify-full,以及显式配置的prefer(先TLS后明文)和allow(先明文后TLS);require和校验模式不会增加明文回退。服务不允许`PGSERVICE`替代Core连接配置。 - -正式构建已切换为 `Dockerfile-base` → `Dockerfile` → `Dockerfile-ee`,`make image-ee` 可完整构建。生产镜像及 `./start.sh managed` 保持 8082;独立 Go CLI 与 `make single` 默认 8083。Docker 数据保存在 `/opt/chen/data`。启动、Db2、多架构和验收步骤见 [构建文档](build.md)。 - -同源反向代理把 `/chen/api/v1/` 路由到 Go;正式镜像不再启动 Java。示例: - -```nginx -location ^~ /chen/api/v1/ { - proxy_pass http://chen:8082; - proxy_http_version 1.1; - proxy_set_header Host $http_host; - proxy_set_header Connection ""; - proxy_buffering off; - proxy_read_timeout 75s; -} -``` - -Luna构建时按已验收范围设置`NUXT_PUBLIC_CHEN_GO_V1_PROTOCOLS=mysql,mariadb,postgresql,oracle,sqlserver,dameng,clickhouse,oceanbase`;Db2 默认内置,也应加入 `db2`;无需 IBM CLI 或专用构建。默认启用已支持的 Go 协议;显式设置空值才保留旧入口。该列表不是运行中自动探测或降级开关。开发代理目标按Luna V1 README设置。浏览器请求使用同源API,不把会话Token放URL。 - -## 连接与执行 - -- Core连接Token通过Wisp交换,浏览器不能提交地址、账号密码、DSN或TLS私钥。成功Ping数据库后才记录连接成功。Go会话Token仅在内存持有。 -- 创建会话需要稳定随机`request_id`。同连接Token与同请求ID可恢复该次请求的结果;另一个请求ID返回409。认证未知结果不能换新请求ID盲重试。 -- 每个控制台固定一个`sql.Conn`,串行执行、元数据查询与补全;事务、临时表、会话变量不会串到其他控制台。数据库实际断线后需显式新建控制台,不自动重放SQL。 -- SQL先由方言解析器或保守语句扫描器确定原文边界,再对整个脚本、各原文语句和解析出的过程内语句执行Core ACL校验;随后逐句执行原文。执行中再次读取MySQL SQL mode和校验权限。脚本不是隐式事务,前序已完成语句不会因后序失败自动回滚。 -- 提交返回202和执行ID。按`request_id`在同控制台去重;相同ID、不同SQL或不同复核选择返回409。结果分页只读取已保存的结果,不重新执行SQL。整数和精确数值以字符串传递;NULL、重复列名、列顺序得到保留。 -- 显式切换数据库分页、计数或完整导出会重新执行来源 SQL。视图、自定义函数和复杂手写 WHERE 等无法证明无副作用的来源,需要逐次确认后提交 `confirm_reexecution`;来源、数据库和连接模式仍绑定在原结果上。此类浏览关闭自动追加,确认不会跳过 ACL、复核或审计。缓存分页保持只读缓存。 -- 取消是独立HTTP动作,通过受控连接调用MySQL KILL QUERY、PostgreSQL pg_cancel_backend或按精确query_id取消ClickHouse查询,并结合驱动取消。SQL Server只有驱动确认attention应答的返回路径才记录已取消;达梦和Db2暂不提供运行中主动取消。数据库已成功返回的结果保持成功;无法确认发送后结果时为`unknown`,禁止自动重试。`canceled`不表示脚本之前的语句已回滚。 - -## 命令复核与会话控制 - -- 命中拒绝规则时,整个脚本不执行;命中复核规则先返回`command_review_required`,用户明确点击“申请复核”才创建Core工单。工单绑定该次执行的原始SQL快照,修改编辑器不会改变待审批SQL。同一脚本的同一ACL/命令组只申请一次复核。 -- 工单批准后再次检查会话权限,再执行原始SQL。驳回、取消、审批超时、会话暂停或关闭都不执行待审SQL。等待审批默认最多30分钟,单独计时,不消耗60秒SQL执行期限。等待期间可取消,即使数据库不支持运行中取消。 -- Core目前保存最多4090个Unicode字符的工单SQL,超长脚本明确拒绝复核,避免仅审批前缀却执行完整脚本。复核没有自动提交、自动批准或未知结果后重新创建工单的行为。 -- `warning`执行并记录风险审计;`notify_and_warn`先返回`command_warning_confirmation_required`,用户确认固定 SQL 后携带`confirm_warnings: true`重新提交。确认不会跳过拒绝或复核规则。只有实际送往数据库的语句才通知;混合脚本中的告警另保留对应审计,避免被已批准记录掩盖通知。 -- 管理员暂停会阻止查询、目录和补全访问,并请求取消正在执行或等待审批的任务。恢复只解除暂停,不自动重放。踢出会话后关闭连接,保留页面SQL草稿供用户查看。 -- Go到Wisp的任务流中断时会话自动暂停,恢复后保留独立的管理员锁定状态。`state_reason`区分控制连接中断和管理员暂停。Wisp v0.2.15未暴露Wisp到Core的上游WebSocket健康状态,这一段不具备同等断线检测。 - -## 传输与API - -浏览器V1链路没有WebSocket。认证、连接、SQL提交、取消、分页、目录与补全均为HTTP。每个会话只建立一个可恢复的fetch-SSE,推送执行状态、会话管理和失联事件;结果行通过分页HTTP获取。断开SSE不会取消SQL、提交事务或销毁数据库会话。 - -所有私有资源都需`Authorization: Bearer `或`token`请求头。资源ID不是授权凭据。错误格式统一为`{"error":{"code":"...","message":"..."}}`。 - -| 方法与路径(前缀`/chen/api/v1`) | 行为 | -| --- | --- | -| GET `/capabilities` | 实际启用的协议与传输能力 | -| POST `/sessions` | `{connect_token,request_id}`,返回`{token,session}` | -| GET / DELETE `/sessions/{sid}` | 状态 / 关闭 | -| GET `/sessions/{sid}/events` | SSE,支持`Last-Event-ID` | -| POST `/sessions/{sid}/consoles` | 可选`{database,schema}`,返回控制台 | -| DELETE `/consoles/{cid}` | 关闭控制台 | -| POST `/consoles/{cid}/executions` | `{sql,request_id,document_version?,submit_review?,confirm_warnings?}`,返回执行状态 | -| GET `/executions/{eid}` | 查询执行及审计记录状态 | -| POST `/executions/{eid}/cancel` | 请求取消,202不等于数据库已取消 | -| GET `/results/{rid}/rows?offset=0&limit=200` | 已物化结果分页 | -| GET `/consoles/{cid}/relations?prefix=...` | 当前权限和连接上下文内的表/视图 | -| GET `/consoles/{cid}/columns?schema=...&table=...` | 列定义 | -| POST `/consoles/{cid}/completion` | `{document,position_utf16,document_version}` | - -执行状态:queued、waiting_review、running、cancel_requested、succeeded、failed、canceled、unknown。`review`包含审批状态、工单链接、复核人和期限;只有明确申请才返回待审工单。`statement_results`保存已完成语句的结果集ID或影响行数。`audit_status=recorded`表示本地outbox已持久化,不表示Core已确认收到。failed表示本地审计/状态持久化失败,与SQL执行结果分别展示。 - -SSE格式为`id: sequence`、`event: type`、`data: {sequence,type,data}`。事件包括execution.updated、console.updated、session.updated、session.closed、export.updated、ai.message和reset。恢复快照为`{session,consoles,executions}`。历史不足时发送reset;订阅溢出时断开,由客户端重新建立并查询状态,不重发SQL。 - -## SQL服务与Monaco - -执行解析使用固定版本的TiDB parser和`pg_query_go/v6`(PostgreSQL 17语法)。前期生成/编译/语料验证未使ANTLR grammars-v4成为更合适的执行解析器;ANTLR项目托管语法不等于数据库厂商完整维护的各版本语法。语法、编辑器容错和服务端执行分别承担责任。 - -Monaco提供编辑、快捷键和补全交互;Go提供当前授权目录、作用域和方言标识符引用规则。补全支持表/列、别名、CTE、派生表、相关子查询、中文/引用名称与基本函数签名;按文档版本丢弃过期响应。用户正在编辑的SQL不会为补全而执行或PREPARE。忙碌控制台返回409,前端保留编辑内容。 - -完整边界见[SQL服务说明](../internal/sqlservice/README.md)。TiDB语法并非完整MySQL语法;当前不支持客户端DELIMITER、MySQL SELECT INTO用户变量;MariaDB RETURNING 由保守扫描器回退支持。复杂函数重载、过程局部变量、动态SQL和完整语义类型推断尚未实现。当前交付不是所有本地客户端功能的替代品。 - -## dev 功能衔接 - -- SQL AI 使用 Core 已启用的模型配置,通过 HTTP 请求及当前会话 SSE 返回消息;schema 工具只访问当前控制台权限,生成 SQL 以提案形式插入编辑器,不自动执行。 -- 对象详情提供列默认值、主外键、索引和一般约束(含 UNIQUE/CHECK),DDL 单独按需加载;PostgreSQL 结构重建与原生视图定义分别标明来源,并过滤当前账号无权查看的列及其依赖对象。 -- 执行计划只准备不运行的 EXPLAIN SQL,随后走普通执行、ACL、告警、复核和审计路径;不自动使用 ANALYZE。 -- Oracle 的 SYSDBA 选项由 Core 连接设置或 single 配置决定;用户名 SYS 不再隐式取得 SYSDBA。SQL Server 尊重平台加密配置与资产 TLS 校验。 - -## 审计、恢复与配额 - -执行前先把意图写入私有目录并fsync;结束后更新状态。执行和拒绝都会记录Core命令审计;原始结果行不重复写入审计。审计摘要包含执行ID,便于从Core关联本地执行记录。字段脱敏发生在结果缓存之前;驱动不能提供字段来源时采用保守脱敏。元数据访问按控制台与操作类型每分钟记录一次访问摘要,避免逐键审计噪声。 - -会话关闭时将 SQL 和已脱敏状态摘要生成为 asciinema v2 录像,通过 Wisp 上传 Core;不在录像重复存放原始结果行。录像成功上传后才删除本地副本;丢失响应后可重建压缩文件重试。Core不可达时,本地outbox保留审计、录像、会话结束和网关清理意图,后台重试并支持进程重启恢复。它不会重放SQL。Wisp UploadCommand没有幂等键,丢失响应可能产生重复审计;CreateForward丢失完整响应时无法找回未返回的forward ID。这些是当前Wisp协议边界。完整授权与生命周期契约见[JumpServer集成说明](../internal/jumpserver/README.md)。 - -默认限制:100个会话、每会话8个控制台、每控制台8个排队任务、每会话256条执行历史、每次执行60秒(含排队,不含已创建工单的审批等待)、100条语句、2MiB SQL、每结果集10000行、每会话16MiB结果行数据。元数据3秒、补全200候选。行限制不代表数据库执行成本限制;大字段在驱动Scan前可能已分配。结果与Token不跨进程保留;未完成意图的结果为未知,重启后需要重新认证并人工确认数据库实际状态。 - -状态目录及outbox分别使用0700/0600。执行意图包含SQL,应按审计数据保护。已完成意图目前需按部署保留策略清理;不要删除未完成意图或未投递outbox。容量限制返回明确错误,不静默丢弃历史或重跑查询。 - -## 验证与放量条件 - -```sh -GOTOOLCHAIN=go1.26.3 go test -race ./... -GOTOOLCHAIN=go1.26.3 go vet ./... -CHEN_INTEGRATION=1 GOTOOLCHAIN=go1.26.3 go test -race -count=1 -v ./internal/engine -run TestIntegrationDatabaseHTTP -``` - -实库测试创建并自动清理专用MySQL 8.4/PostgreSQL 16容器,使用随机localhost端口;不连接现有业务库。Wisp测试使用真实生成的gRPC客户端/服务端内存通信,并注入Core响应。Luna同时提供Monaco组件smoke和真实工作区联调脚本。详细证据以当前运行记录为准。 - -放量前仍需现网版本Core/Wisp、真实资产权限、网关、数据库TLS、管理员锁定/踢出、审计落Core和反向代理空闲重连验收。ACL 已支持常见 Java 前后查找、反向引用及字面引用;不能编译的模式在认证时拒绝,匹配超时按拒绝处理,完整 Java Pattern 语法及非密码认证仍需独立适配。带人脸监控令牌的连接先向 Wisp 注册,失败即关闭会话;真实摄像头流程仍需部署验收。表格写入、CSV 导入及结构编辑已接入,详细事务与类型边界见[写入说明](writes.md)。UI 范围不能代替数据库权限或 Core ACL,部署只读访问应使用只读数据库账号。 - -### 2026-10-26 托管模式实测 - -使用本机隔离Core数据库、独立Redis、Wisp v0.2.15及专用Celery worker,未改现有Core数据库;测试账号、资产、授权、工单和合成SELECT均属于该隔离环境。`TestManagedLive`通过真实HTTP/gRPC验证: - -- 普通操作员连接Token、资产/账号授权、复制/粘贴禁用标志;撤销授权后拒绝新连接。 -- SQL允许/拒绝,整批提前拒绝,显式复核、原始SQL快照、批准、驳回、取消和超时;暂停待审批会关闭工单并阻止SQL。 -- 管理员暂停、恢复、踢出,以及Core会话结束记录。 -- Core命令审计及实际风险等级、NotifyWarning显式确认与站内通知;未执行的后句单独告警及解析失败不会误发告警。 -- 字段、NULL及无法确定原列的别名脱敏。 -- 会话结束后 Core 持久化的 gzip 录像可重新打开并解码为 asciinema v2。 - -`TestManagedLivePermissionRevocation`另观察了Wisp原有五分钟检查周期实际发出的撤权事件,保留现有十分钟宽限;不是实时撤权保证。真实数据库与API验证之外,Luna用实际Nuxt/Monaco配合模拟HTTP/SSE验证审批与暂停竞态。没有把模拟控制浏览器测试描述为真实Core页面端到端。 - -网关转发、TLS资产配置、审计断网重启恢复和控制流断开/恢复有契约或故障注入测试,未在本轮做真实部署验收。人脸监控有 Wisp 注册失败补偿契约测试,尚未做摄像头部署验收;Java专有正则和非密码认证仍需旧入口。邮件未对外发送,测试通知限于隔离Core站内信和本地邮件后端。 - -以下为初版 MySQL/PostgreSQL 的历史验收记录;当前扩展的结果以数据库兼容性文档为准。 - -初版开发验收:干净模块缓存`go mod verify`通过;Go完整race/实库与Web+Electron类型检查、Nuxt生产构建通过。MySQL和PostgreSQL分别在真实Nuxt工作区的Chromium验收中完成认证、语义补全、精确值查询、本地审计、取消与关闭。报告保存在Luna `output/chen-v1-browser/mysql/`和`postgresql/`,其中明确标注Core/Wisp为测试边界。 - -最终回归使用单独下载的干净模块缓存,`go mod verify`、全包`go test -race`和`go vet`均通过。实库HTTP整套20.76秒:MySQL8.4普通账号13.59秒,PostgreSQL16普通账号7.17秒;均包含服务器侧取消、超时和关闭验证。初版独立 Go 构建已成功生成Linux arm64镜像`chen-go:v1-local`;真实启动验证健康接口、能力接口、UID10001、审计目录0700及SIGTERM退出码0。临时数据库容器、浏览器fixture、Nuxt测试服务和镜像smoke卷均已清理。 diff --git a/docs/single.md b/docs/single.md deleted file mode 100644 index 043767e..0000000 --- a/docs/single.md +++ /dev/null @@ -1,106 +0,0 @@ -# Chen single:从配置文件直接连接数据库 - -`single` 独立运行 Go 后端和 Luna Monaco 工作区,不创建 Wisp 客户端,不调用 Core API。数据库地址、账号、密码和 TLS 配置只从本地 YAML 读取,浏览器 URL 只指定 `selector`。支持旧版数据库家族;Db2 使用内置的纯 Go DRDA 驱动,无需额外客户端或构建标签,详见[数据库兼容性及实测](database-compatibility.md)。 - -## 构建和启动 - -需要 Go 1.26.3、C 编译器、Node.js 24.14+ 和 Luna 所声明的 pnpm 版本。以下示例假定 Chen、Luna 为相邻目录。 - -```sh -# 在 Luna 目录生成静态工作区 -cd ../luna -pnpm install --frozen-lockfile -pnpm generate:chen - -# 在 Chen 目录构建 Go 程序 -cd ../chen -mkdir -p data/bin -GOTOOLCHAIN=go1.26.3 go build -o data/bin/chen-go ./cmd/chen-go -cp conf/standalone_backends_example.yaml data/standalone_backends.yml -chmod 600 data/standalone_backends.yml -# 编辑 data/standalone_backends.yml,填写自己的数据库信息 - -./data/bin/chen-go single \ - --backends data/standalone_backends.yml \ - --web-dir ../luna/.output/public \ - --state-dir data/single -``` - -然后打开: - -```text -http://127.0.0.1:8083/luna/chen/?selector=local-mysql -http://127.0.0.1:8083/luna/chen/?selector=local-pg -``` - -根地址 `http://127.0.0.1:8083/?selector=local-mysql` 也会跳转到相同工作区。`standalone` 是 `single` 的别名,`-b` 是 `--backends` 的别名;也支持 `chen-go --single ...`。不带 `single` 时继续使用托管模式。只需要 HTTP API 时可省略 `--web-dir`。 - -Go 直接提供 `/luna/` 下的生成文件和 `/chen/api/v1/`,运行时不需要 Node.js、Nuxt 开发服务器或额外反向代理。静态文件须使用默认 `/luna/` base 构建。编辑器 worker、图标和语言资源均来自本地静态目录。 - -更新页面时先完成 `pnpm generate:chen`,再重启 Chen。Nuxt 会替换输出目录,正在运行的安全文件根仍指向旧目录;仅刷新浏览器不能切换该目录。 - -## 配置 - -```yaml -version: 1 -backends: - - name: Local MySQL - selector: local-mysql - protocol: mysql - host: 127.0.0.1 - port: 3306 - user: chen - password: replace-with-database-password - database: app - use_ssl: false - - name: Local PostgreSQL - selector: local-pg - protocol: postgresql - host: 127.0.0.1 - port: 5432 - user: chen - password: replace-with-database-password - database: app - use_ssl: false -``` - -`selector` 全局唯一、区分大小写、精确匹配;未知或缺失 selector 不连接任何默认数据库。配置在启动时校验和读取,修改后重启生效。数据库权限由配置的数据库账号决定;浏览器不能覆盖地址或凭据。与 Magnus 的协议代理相比,这里不需要客户端数据库密码,因为客户端是浏览器工作区。 - -配置文件采用严格 YAML 校验,不接受未知字段、多个文档、重复 selector、不支持的协议、符号链接和超过 1 MiB 的文件。默认要求文件不向组或其他用户开放权限(建议 `0600`);特殊挂载环境可显式使用 `--allow-insecure-backend-file`。TLS 选项与完整示例见 [配置示例](../conf/standalone_backends_example.yaml)。 - -single 不验证 Core 用户身份,selector 是连接名称,不是密码;默认仅监听 `127.0.0.1:8083`,且拒绝陌生 Host 对本机监听器的访问。若显式改为网络监听,应由部署方控制访问范围或通过认证反向代理接入。 - -## 会话、审计和传输 - -页面先确认能力接口为 `mode: single`,再发送 `POST /chen/api/v1/sessions` 的 `{selector, request_id}`。服务端返回独立的会话 token,后续请求仍校验 token 和资源归属。托管模式拒绝 selector,single 模式拒绝 Core token;两者不会互相降级。 - -同一浏览器请求重试复用相同会话;同一 selector 在不同标签页或重新打开页面时建立独立会话和数据库连接,不共享事务、临时表或查询结果。每个控制台保持自己的物理连接;取消、超时、关闭会话的行为与 Go V1 一致。单机会话默认闲置 15 分钟后关闭,最长持续 8 小时。 - -认证、查询、取消、分页和补全使用 HTTP,每会话一个 SSE 推送状态,没有业务 WebSocket。SSE 中断不重放 SQL。 - -本地审计以独立 JSON 文件保存在 `--state-dir` 下的 `audit/`,执行意图保存在 `executions/`。生命周期、元数据访问摘要、SQL 及执行结果状态落盘并同步写入;不保存配置中的数据库凭据或原始结果行。SQL 原文本身仍应按审计数据保护。`audit_status=recorded` 表示本地持久化完成。single 不具有 Core ACL、字段脱敏、管理员远程控制或 Core 审计投递能力。 - -会话和结果保存在进程内;重启后重新连接,不恢复事务或自动重放 SQL。旧执行意图可用于人工核对未知结果。资源配额、语法边界和取消语义沿用 [Go V1 说明](go-v1.md)。 - -## 验证 - -```sh -GOTOOLCHAIN=go1.26.3 go test -race ./... -GOTOOLCHAIN=go1.26.3 go vet ./... -CHEN_INTEGRATION=1 GOTOOLCHAIN=go1.26.3 go test -race -count=1 -v ./internal/engine -run TestIntegrationSingle -``` - -实库测试自行启动和清理专用 MySQL 8.4、PostgreSQL 16 容器,使用普通数据库账号,覆盖 selector、会话隔离、查询、补全、取消及本地审计。静态工作区浏览器验收见 Luna 的 Chen V1 README。 - -初版 2026-10-23 本机验收(当前多库扩展另见兼容性文档):两库实库 HTTP 集成测试、全包 `-race`、`go vet`、干净模块缓存校验通过。真实 `chen-go single` 进程直接提供生成的 Luna 静态文件,两库均通过 Chromium 的 URL 直连、Monaco 别名补全、精确整数/中文/NULL 查询、双标签页独立会话、取消及关闭验收;网络记录中 Core/Wisp 请求和 WebSocket 均为零。审计目录 `0700`、文件 `0600`,连接生命周期和查询记录实际落盘;进程正常退出。报告位于 Luna `output/chen-single-browser/mysql/report.json` 与 `postgresql/report.json`。 - -## 新增方言配置 - -`protocol` 支持 `mysql`、`mariadb`、`postgresql`、`oracle`、`sqlserver`、`db2`、`dameng`、`clickhouse`、`oceanbase`。OceanBase 必须同时设置 `tenant_mode: mysql` 或 `oracle`,不能把 Oracle 租户交给 Oracle 原生驱动。`oceanbase-mysql` / `oceanbase-oracle` 也是明确的兼容写法。 - -- Oracle 的 `database` 为 service name;可用 `schema` 指定当前 schema。 -- Dameng 的 `schema` 指定模式,不应把模式误当成 Oracle service。 -- Oracle 可显式设置 `use_sysdba: true`;默认普通登录,用户名 SYS 不会隐式提升权限。 -- ClickHouse 的 `transport` 可为 `native` 或 `http`;默认按常用端口选择。 -- `can_cancel: false` 表示当前驱动无法中断已经开始的查询,界面不会承诺能够取消;Db2 当前属于此范围。 -- 此配置文件含数据库凭据,必须保持 `0600`;浏览器只使用 selector,不能指定任意地址或账号。 diff --git a/docs/writes.md b/docs/writes.md deleted file mode 100644 index 84f6d90..0000000 --- a/docs/writes.md +++ /dev/null @@ -1,43 +0,0 @@ -# 数据写入与结构修改 - -对照 dev 的 Java Chen,Go/Luna 工作区接入了表格增删改、选中行批量赋值、CSV 导入草稿、建表、字段及索引修改,以及 SQL 文件载入。浏览器仍只使用 HTTP 与每会话一条 SSE;没有为写入新增 WebSocket。 - -## 操作流程 - -- 从对象树打开表数据,可新增、删除、修改行;单表直接投影的查询结果可修改字段。单元格 F2 打开编辑,双击保留大字段查看。修改保留在草稿中,点击保存后先预览 SQL,再确认提交。 -- NULL、空字符串和新增行的 DEFAULT 分开表示。批量修改只作用于选择的行。未保存时阻止翻页、换结果、导出和丢弃工作区,避免保存到错误快照。 -- 保存成功后刷新数据,重新取得可信行标识;并发冲突、结构变化、计划过期均要求重新载入。连接中断且数据库结果不明时显示 unknown,不自动重试。 -- CSV 先解析、映射列并形成草稿,与普通网格共用预览和保存。文件上限 10 MiB、每次最多 500 条变更,并校验 2 MiB 提交预算;不会把大文件悄悄拆成多个可能部分成功的事务。SQL 文件上限 1 MiB,按 UTF-8 载入新编辑器,不直接执行。两者在托管模式均受 Core upload 权限控制;single 模式由本地配置账号权限决定。 -- 对象树提供建表、修改字段、创建/删除索引入口,先生成可审阅 SQL,确认后走普通执行路径。对于可能丢失默认值、注释、生成属性的字段修改,不生成不完整的替换定义;提供完整 DDL 进入编辑器核对。主键、唯一及约束索引不提供快捷删除,需检查完整 DDL 后手动处理。结构修改及普通脚本不承诺整批事务回滚,数据库可能隐式提交 DDL。 - -## 网格可编辑条件 - -需要真实基础表、单列主键和可确认来源的直接投影。主键、生成列、自增列、已脱敏字段不可直接修改;新增行可省略生成列以采用数据库默认值。主键原值保留在服务器,浏览器只提交不透明 row_ref。 - -JOIN、CTE、表达式、聚合、DISTINCT、多个结果集、视图、无主键或复合主键结果保持只读,并显示原因。这些限制与旧版的单表/单列主键设计一致。二进制、数组、空间类型、用户自定义类型及需要驱动专门处理的 LOB 暂不提供网格赋值,仍可查看或通过 SQL 修改。ClickHouse 不提供事务网格保存,可通过 SQL 执行其支持的写入和结构操作。 - -MySQL/MariaDB/OceanBase MySQL 网格只接受 InnoDB 基础表,避免非事务表产生半批提交。Oracle 空字符串按数据库语义成为 NULL。精确整数和小数从浏览器到驱动保持字符串,不经 JavaScript number。 - -## 服务端提交与审计 - -服务器根据固定的结果来源、真实列元数据和原始值生成参数化写入计划,不接受客户端 SQL、WHERE 或主键作为执行依据。计划默认 15 分钟有效,同一计划不能重复落库;相同 request_id 仅返回同一个执行。每次最多 500 条变更,SQL 预览上限 2 MiB。 - -整批及每条 SQL 在开始写入前均做 Core ACL、告警确认及工单复核。审批绑定不可变计划;待审期间暂停、关闭、拒绝或取消均不会写入。执行前记录意图,执行后记录提交、暂存、回滚或未知状态;审计投递失败不会把已提交数据库操作误报成已回滚。 - -空闲的自动提交连接使用独立事务;已处于可识别用户事务时使用保存点,保存成功仅显示“已暂存”,不会提交外层事务。Oracle 用服务器事务 ID 识别活动事务,并在保存点操作期间关闭驱动隐式自动提交,结束后恢复原驱动设置;执行计划辅助操作共用此保护。无法可靠识别的事务状态拒绝网格保存。更新用主键及被修改字段的原始值检测冲突,删除检查可比较的原始字段;任一操作影响行数不等于 1 时整批回滚。未参与比较的字段发生变化不一定被检测到,这不是全表版本锁。 - -普通 SQL 使用各方言解析器或保守语句边界检查,保留权限管理、序列及其他管理 DDL。Oracle/SQL Server 使用原生语法树识别过程及控制块,并对嵌套原文语句做 ACL 校验;达梦和 OceanBase Oracle 的兼容过程使用 Oracle 语法证明边界,再由目标数据库校验。T-SQL 过程/函数/触发器/视图定义需单独提交,避免将过程体误当成后续独立命令。客户端 GO/DELIMITER、不能证明边界的过程语法和含糊的无分号批处理仍拒绝;数据库负责最终语法与权限校验。 - -复制 SQL 支持 Db2 常见标量结果;ClickHouse 支持复制 INSERT,排除 ALIAS/MATERIALIZED 生成列。ClickHouse 排序键不构成唯一身份,不生成依赖该键的 UPDATE,不提供事务网格保存。无法准确编码的值明确报错,不产生截断或替换后的 SQL。 - -## 验证入口 - -写测试必须独立显式开启;只读矩阵变量不会触发写操作。测试只创建唯一的 CHEN_W_/CHEN_E_/CHEN_M_ 表,并清理自己创建的准确表名,不修改业务表。 - -```sh -go test -race ./... -CHEN_WRITE_BACKENDS=/private/backends.yml go test ./internal/engine -run 'Test(WriteCommands|GridEdits)Live' -count=1 -v -CHEN_WRITE_MANAGED=1 CHEN_MANAGED_FIXTURE=/private/fixture.json go test ./internal/engine -run '^TestGridEditsManagedLive$' -count=1 -v -``` - -Db2 默认使用纯 Go DRDA 驱动,无需 IBM CLI 或构建标签;更换驱动后的实库写入、保存点及类型兼容须重新验收,见[构建说明](build.md)。Core/Wisp 写入验收使用隔离本机环境,包含拒绝、待审无写入、批准、告警确认、暂停取消、上传权限、真实 Core 命令审计和会话录像。 diff --git a/go.mod b/go.mod index 55a9121..52e909d 100644 --- a/go.mod +++ b/go.mod @@ -24,68 +24,26 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/azure-pipeline-go v0.2.3 // indirect - github.com/Azure/azure-storage-blob-go v0.15.0 // indirect github.com/ClickHouse/ch-go v0.74.0 // indirect - github.com/LeeEirc/httpsig v1.2.1 // indirect - github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect github.com/andybalholm/brotli v1.2.6 // indirect - github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/aws/aws-sdk-go v1.44.306 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/elastic/elastic-transport-go/v8 v8.6.0 // indirect - github.com/elastic/go-elasticsearch/v6 v6.8.5 // indirect - github.com/elastic/go-elasticsearch/v8 v8.14.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/errors v0.7.1 // indirect - github.com/go-logr/logr v1.4.4 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.3 // indirect - github.com/huaweicloud/huaweicloud-sdk-go-obs v3.25.4+incompatible // indirect - github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/influxdata/influxdb-client-go/v2 v2.14.0 // indirect - github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jumpserver-dev/sdk-go v0.0.0-20260821083952-82cf2e23d207 // indirect github.com/klauspost/compress v1.19.1 // indirect - github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect - github.com/mattn/go-ieproxy v0.0.1 // indirect - github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/paulmach/orb v0.13.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pierrec/lz4/v4 v4.1.27 // indirect github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect github.com/pingcap/log v1.1.0 // indirect - github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/segmentio/asm v1.2.1 // indirect - github.com/shirou/gopsutil/v3 v3.24.5 // indirect - github.com/shoenig/go-m1cpu v0.1.7 // indirect - github.com/sirupsen/logrus v1.9.4 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/cobra v1.10.1 // indirect - github.com/spf13/pflag v1.0.10 // indirect - github.com/spf13/viper v1.21.0 // indirect - github.com/subosito/gotenv v1.6.0 // indirect - github.com/tklauser/go-sysconf v0.4.0 // indirect - github.com/tklauser/numcpus v0.12.0 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/otel v1.46.0 // indirect - go.opentelemetry.io/otel/metric v1.46.0 // indirect go.opentelemetry.io/otel/trace v1.46.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -95,7 +53,6 @@ require ( golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect - golang.org/x/time v0.12.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect ) diff --git a/go.sum b/go.sum index 3bb3a12..67b5722 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -gitee.com/chunanyong/dm v1.8.23 h1:IadSVTP3l1qv41yzTaHffpsAdNtyR3Bl2FwR9Xpgpb8= -gitee.com/chunanyong/dm v1.8.23/go.mod h1:EPRJnuPFgbyOFgJ0TRYCTGzhq+ZT4wdyaj/GW/LLcNg= -github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1/go.mod h1:iptorS+VYKFL2N6PnebpS91dubG35eAOEERnT4PJbQU= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 h1:u93s+zU2JD62im61Bm5CZIc1ZrOJaIAWEg0WOrMVkEo= @@ -14,14 +12,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0 h1:MaKvxE6 github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.5.0/go.mod h1:i2h9fsTFKZorh8RdV2IcSUf/Qj98GlTkrTvUbX/s8as= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= -github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= -github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 h1:Nljr4q1GRA/5vCrMONS+g4u4LRHNgOXVSh3O43J2CnI= github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0/go.mod h1:Y33QHnf0FfdVewFFISOGe20mkZbxX4H839o955/PoeI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -29,53 +21,53 @@ github.com/ClickHouse/ch-go v0.74.0 h1:uYs2m4wIt0ZHSM1E72rg0maCfzhR2V3xWb/vZEgpe github.com/ClickHouse/ch-go v0.74.0/go.mod h1:sZ/r+8ttZMjyrP9PuFbgoVbth1ywIu2LIQNA2vgko6M= github.com/ClickHouse/clickhouse-go/v2 v2.49.0 h1:HgiLTraLGgMe8kYivQ6RzMHDUkU/uFUpsWEAuhVk34Q= github.com/ClickHouse/clickhouse-go/v2 v2.49.0/go.mod h1:o8dSgGJGxY7CeWFNiKusF9a5RnLpgzuo7OaNyH0NQPE= -github.com/LeeEirc/httpsig v1.2.1 h1:GGmCc2Bug3KeCchlZHwrfyjyAnw+JlzMjKDobPypirs= -github.com/LeeEirc/httpsig v1.2.1/go.mod h1:aoLZLXCSNDgkzsH2sGLWn3hlVbF+Voe8fCArxLt9nWA= -github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Zxc4wonder/omni v0.0.0-20261028061125-c1e38b7dfe51 h1:sYapCnqKvMpyQeF/6jbza5aNAnx00LFOXKp8YtQBM0Q= github.com/Zxc4wonder/omni v0.0.0-20261028061125-c1e38b7dfe51/go.mod h1:9XZE1hqtvr09Oc+DdLu1/yCb8MDcoy/EPdA4AONDHlE= -github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= -github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/brotli v1.2.6 h1:ftYnfj6usCp+UGV5kSJ3+chpMQgU+gJf/AxsUQ52REI= github.com/andybalholm/brotli v1.2.6/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= -github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= -github.com/aws/aws-sdk-go v1.44.306 h1:H487V/1N09BDxeGR7oR+LloC2uUpmf4atmqJaBgQOIs= -github.com/aws/aws-sdk-go v1.44.306/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo= github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/elastic/elastic-transport-go/v8 v8.6.0 h1:Y2S/FBjx1LlCv5m6pWAF2kDJAHoSjSRSJCApolgfthA= -github.com/elastic/elastic-transport-go/v8 v8.6.0/go.mod h1:YLHer5cj0csTzNFXoNQ8qhtGY1GTvSqPnKWKaqQE3Hk= -github.com/elastic/go-elasticsearch/v6 v6.8.5 h1:U2HtkBseC1FNBmDr0TR2tKltL6FxoY+niDAlj5M8TK8= -github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI= -github.com/elastic/go-elasticsearch/v8 v8.14.0 h1:1ywU8WFReLLcxE1WJqii3hTtbPUE2hc38ZK/j4mMFow= -github.com/elastic/go-elasticsearch/v8 v8.14.0/go.mod h1:WRvnlGkSuZyp83M2U8El/LGXpCjYLrvlkSgkAH4O5I4= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= +github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= -github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= @@ -89,21 +81,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/huaweicloud/huaweicloud-sdk-go-obs v3.25.4+incompatible h1:yNjwdvn9fwuN6Ouxr0xHM0cVu03YMUWUyFmu2van/Yc= -github.com/huaweicloud/huaweicloud-sdk-go-obs v3.25.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= -github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70 h1:muF5XqVkHnMdbMDXusPdKtuT8qWzefBgSuLH1JVHcC4= -github.com/ibmruntimes/go-recordio/v2 v2.0.0-20240416213906-ae0ad556db70/go.mod h1:NSpUK0x9IyEoM1EjTp2/S8ErxZfRHoA2DfwiYobFSkc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4= -github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI= -github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf h1:7JTmneyiNEwVBOHSjoMxiWAqB992atOeepeFYegn5RU= -github.com/influxdata/line-protocol v0.0.0-20210922203350-b1ad95c89adf/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -112,38 +91,47 @@ github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= -github.com/jumpserver-dev/sdk-go v0.0.0-20260821083952-82cf2e23d207 h1:hbYIaxsxF47XyTGRxxKt74lmPaGjjNlYBomnaSEhnR8= -github.com/jumpserver-dev/sdk-go v0.0.0-20260821083952-82cf2e23d207/go.mod h1:QmnL7BRb74/XY1nu8kWQWs6DHXt3uIaWkDCkQSRyZlM= -github.com/jumpserver/wisp v0.2.12 h1:fg4Twh9DlXUzuq/s2eJzYE3A3zm0EKh/CvZECT89o9s= -github.com/jumpserver/wisp v0.2.12/go.mod h1:3L3V/kMpvw7ugXmuCcd8WA9DqcQW6cL5VbC0uEyVbbI= github.com/jumpserver/wisp v0.2.15 h1:Bh4eK9jtjBAQs2t0lPRdqAknJ1yQ1nQNBozJvObbJWg= github.com/jumpserver/wisp v0.2.15/go.mod h1:1o4qBIW4hFgrwaKa6MWlVNkY8MffCS50Q8Z4b7JjEF4= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= -github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/microsoft/go-mssqldb v1.11.2 h1:FCgeBIK8um2+X4tbun6Q71N1KsfyCDPKY41e1yGVjSE= github.com/microsoft/go-mssqldb v1.11.2/go.mod h1:CYgwG5AMXFojbjTg+GNP5G/y6uz1BhTyZaPqQWzkGnQ= -github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= -github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.3.0 h1:nos4BtzzUIqB406BgQnWGMI4qib9BZ8XUHU+ucv/n1c= +github.com/moby/go-archive v0.3.0/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE= +github.com/moby/moby/api v1.56.1 h1:PpWkvVPB7Fr/No8w+TfyJ/I6rWZ2YPZhT3JorIg+06c= +github.com/moby/moby/api v1.56.1/go.mod h1:sZ+THbVWkjOmBPPfbnzdD/G1LuIexWhqlSHHPTDQ1Uk= +github.com/moby/moby/client v0.6.0 h1:AJjEB21QPbXSXjDsZorFBoDZPhMrfbpaPLgSMAW9Bgs= +github.com/moby/moby/client v0.6.0/go.mod h1:OCo00wNRyA3m4lmJ228W3JbyCN4ZNNYjpOXiJydBdcQ= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= +github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= +github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= +github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/oceanbase/go-oceanbase-driver v1.7.0-oceanbase.1 h1:gPmpZPIhmadIoSrewLtv/U/tZz8yPKwuvsOUq3ExtOQ= github.com/oceanbase/go-oceanbase-driver v1.7.0-oceanbase.1/go.mod h1:bEmS9wEJeKtLsYCtwEj61ut+bSPrRFVTfvwaT9mrIqo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/paulmach/orb v0.13.0 h1:r7n7mQGGF+cj/CbcivEj9J3HGK+XR+yXnvzRdq9saIw= github.com/paulmach/orb v0.13.0/go.mod h1:6scRWINywA2Jf05dcjOfLfxrUIMECvTSG2MVbRLxu/k= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pganalyze/pg_query_go/v6 v6.2.5 h1:i7dvkA5167th3rXtk0jv9+r5DeJd4GqeGOVKuMTda8s= github.com/pganalyze/pg_query_go/v6 v6.2.5/go.mod h1:JZoURQupTV7G8lS6OzKakgvp+xpwu7+dH5kA5WrikzM= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= @@ -160,58 +148,41 @@ github.com/pingcap/tidb/pkg/parser v0.0.0-20241218021942-d13e52ed6e22/go.mod h1: github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= -github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= -github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= -github.com/shoenig/go-m1cpu v0.1.7 h1:C76Yd0ObKR82W4vhfjZiCp0HxcSZ8Nqd84v+HZ0qyI0= -github.com/shoenig/go-m1cpu v0.1.7/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w= +github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs= +github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg= -github.com/sijms/go-ora/v2 v2.9.0/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= -github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= -github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw= github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= +github.com/testcontainers/testcontainers-go v0.44.0 h1:/Fwh6HY1mIikhnm9e7HwoxGycx0lzRAE0f5VQpjFxzI= +github.com/testcontainers/testcontainers-go v0.44.0/go.mod h1:IcnwQrYTO86xHXu5bvMaBH7ATlbS3Qn1M1QWW3c66rE= +github.com/testcontainers/testcontainers-go/modules/mssql v0.41.0 h1:YAuSa+IMTn0nA6iNvSAFgJEeJsQWSghFWu0ubebWXJI= +github.com/testcontainers/testcontainers-go/modules/mssql v0.41.0/go.mod h1:3leIIvry5kLZr8uxMIDX0iL2TRwVx2UDDSkGJGlpsvQ= github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8= @@ -240,59 +211,27 @@ go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= @@ -300,13 +239,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= @@ -314,6 +250,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -github.com/go-db2/go-db2 v0.0.0-20261103115748-31eae67a759d h1:2hisdFchqEH3aY7cvtKliDbuKuRMYOXQoLGIkMkA75E= -github.com/go-db2/go-db2 v0.0.0-20261103115748-31eae67a759d/go.mod h1:Pprjgai+gEyBEjRo2sWcU99LQovT7ZxcZiK2wnjNb+E= diff --git a/internal/engine/database_dameng_test.go b/internal/engine/database_dameng_test.go index e71b1c7..f56b2a0 100644 --- a/internal/engine/database_dameng_test.go +++ b/internal/engine/database_dameng_test.go @@ -241,7 +241,7 @@ func TestDamengSocketBoundsEachReadAndWrite(t *testing.T) { func TestDamengTransportErrorsCannotBecomeKnownSQLFailures(t *testing.T) { for _, code := range []int32{6001, 6060, 20000, 20001} { vendorErr := &dm.DmError{ErrCode: code, ErrText: "private endpoint"} - if isServerError(vendorErr) || !isTransportError(vendorErr) { + if isServerError(vendorErr) || !isDamengTransportError(vendorErr) { t.Fatalf("transport code %d was misclassified", code) } raw := &damengTestConn{} diff --git a/internal/engine/database_db2_test.go b/internal/engine/database_db2_test.go deleted file mode 100644 index 035ec85..0000000 --- a/internal/engine/database_db2_test.go +++ /dev/null @@ -1,102 +0,0 @@ -//go:build ibmdb - -package engine - -import ( - "database/sql" - "os" - "strings" - "testing" - "time" - - "github.com/jumpserver/chen/internal/jumpserver" - "github.com/jumpserver/chen/internal/standalone" -) - -func readonlyDb2Fixture(t *testing.T, timeout int) (*sql.DB, func(error) string) { - t.Helper() - path := os.Getenv("CHEN_READONLY_BACKENDS") - if path == "" || os.Getenv("CHEN_READONLY_SELECTOR") != "qa-db2" { - t.Skip("explicit Db2 test fixture not selected") - } - cfg, err := standalone.Load(path, standalone.LoadOptions{}) - if err != nil { - t.Fatal("invalid private configuration") - } - for _, b := range cfg.Backends { - if b.Selector != "qa-db2" { - continue - } - dsn, err := db2DSN(jumpserver.ConnectionConfig{Host: b.Host, Port: b.Port, User: b.User, Password: b.Password, Database: b.Database}, timeout) - if err != nil { - t.Fatal("invalid Db2 settings") - } - db, err := sql.Open("go_ibm_db", dsn) - if err != nil { - t.Fatal("cannot construct Db2 connection") - } - t.Cleanup(func() { db.Close() }) - if err = db.Ping(); err != nil { - t.Fatal("cannot connect to Db2 fixture") - } - return db, func(err error) string { - message := err.Error() - for _, secret := range []string{b.Password, b.Host, b.User, b.Database} { - if secret != "" { - message = strings.ReplaceAll(message, secret, "[redacted]") - } - } - return message - } - } - t.Fatal("selected Db2 fixture missing") - return nil, nil -} - -// No application data is accessed: the timed query generates integers in a CTE. -func TestDb2CLIQueryTimeout(t *testing.T) { - db, _ := readonlyDb2Fixture(t, 1) - started := time.Now() - var sum int64 - err := db.QueryRow("WITH counter(n) AS (VALUES 1 UNION ALL SELECT n+1 FROM counter WHERE n<100000000) SELECT SUM(BIGINT(n)) FROM counter").Scan(&sum) - elapsed := time.Since(started) - if err == nil { - t.Fatal("synthetic query completed without exercising CLI timeout") - } - if !strings.Contains(err.Error(), "57014") || elapsed > 10*time.Second { - t.Fatalf("CLI timeout was not enforced: elapsed=%s SQLSTATE57014=%v", elapsed, strings.Contains(err.Error(), "57014")) - } - t.Logf("CLI QueryTimeout=1 returned SQLSTATE 57014 in %s", elapsed.Round(time.Millisecond)) - if err = db.QueryRow("VALUES 1").Scan(&sum); err != nil || sum != 1 { - t.Fatal("connection not reusable after CLI timeout") - } -} - -func TestDb2WideValues(t *testing.T) { - db, safe := readonlyDb2Fixture(t, 60) - var national, binary, precise []byte - err := db.QueryRow("SELECT CAST('中文😀' AS DBCLOB(1M)), BLOB('Hello'), CAST('9007199254740993.123456789' AS DECIMAL(31,9)) FROM SYSIBM.SYSDUMMY1").Scan(&national, &binary, &precise) - if err != nil { - t.Fatal(safe(err)) - } - if string(national) != "中文😀" { - t.Fatalf("DBCLOB Unicode conversion changed: %x", national) - } - if string(binary) != "Hello" { - t.Fatal("ASCII BLOB changed") - } - if string(precise) != "9007199254740993.123456789" { - t.Fatal("DECIMAL precision changed") - } - var wide, empty []byte - err = db.QueryRow("SELECT REPEAT(CAST('中文😀' AS DBCLOB(3M)),150000)||REPEAT(CAST('中文😀' AS DBCLOB(3M)),150000), CAST('' AS DBCLOB(1M)) FROM SYSIBM.SYSDUMMY1").Scan(&wide, &empty) - if err != nil { - t.Fatal(safe(err)) - } - if string(wide) != strings.Repeat("中文😀", 300000) { - t.Fatalf("large DBCLOB changed: got %d bytes", len(wide)) - } - if empty == nil || len(empty) != 0 { - t.Fatal("empty DBCLOB became NULL") - } -} diff --git a/internal/engine/execution.go b/internal/engine/execution.go index 2938ad6..2cde80a 100644 --- a/internal/engine/execution.go +++ b/internal/engine/execution.go @@ -3,14 +3,11 @@ package engine import ( "context" "database/sql" - "database/sql/driver" "encoding/hex" "encoding/json" "errors" "fmt" - "io" "math" - "net" "net/http" "os" "path/filepath" @@ -741,19 +738,6 @@ func isServerError(err error) bool { return errors.As(err, &me) || errors.As(err, &pe) || extraDatabaseError(err) != nil } -// A transport failure after dispatch cannot prove whether a write committed. -func isTransportError(err error) bool { - if isDamengTransportError(err) { - return true - } - var me *mysql.MySQLError - var pe *pgconn.PgError - if errors.As(err, &me) || errors.As(err, &pe) || extraDatabaseError(err) != nil { - return false - } - var ne net.Error - return errors.Is(err, driver.ErrBadConn) || errors.Is(err, sql.ErrConnDone) || errors.Is(err, mysql.ErrInvalidConn) || errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.As(err, &ne) || pgconn.SafeToRetry(err) == false && strings.Contains(err.Error(), "conn closed") -} func binaryResultType(dbType string) bool { dbType = strings.ToUpper(dbType) return strings.Contains(dbType, "BLOB") || strings.Contains(dbType, "BINARY") || dbType == "BYTEA" || dbType == "RAW" || dbType == "LONG RAW" || dbType == "IMAGE" diff --git a/internal/engine/explain.go b/internal/engine/explain.go index cd20419..fbae8fd 100644 --- a/internal/engine/explain.go +++ b/internal/engine/explain.go @@ -3,20 +3,12 @@ package engine import ( "context" "encoding/hex" - "errors" "net/http" "strings" "github.com/jumpserver/chen/internal/sqlservice" ) -func prepareExplainSQL(dialect, document string) (string, error) { - if specialPlanDialect(dialect) { - return "", errors.New("explain_requires_plan_execution") - } - return sqlservice.EstimatedPlanSQL(sqlservice.Dialect(dialect), document, sqlservice.AnalyzeOptions{}) -} - func (v *Server) prepareExplain(w http.ResponseWriter, r *http.Request) { s := v.auth(w, r, false) if s == nil { diff --git a/internal/engine/export_test.go b/internal/engine/export_test.go index e6f9a2c..5821a08 100644 --- a/internal/engine/export_test.go +++ b/internal/engine/export_test.go @@ -1,6 +1,7 @@ package engine import ( + "encoding/json" "net/http/httptest" "strings" "testing" @@ -22,20 +23,26 @@ func TestLegacyResultExportRouteIsUnavailable(t *testing.T) { } func TestExplainOnlyProducesEstimatedSingleReadSQL(t *testing.T) { - for _, dialect := range []string{"mysql", "mariadb", "postgresql", "clickhouse", "oceanbase-mysql"} { - sql, err := prepareExplainSQL(dialect, "SELECT 1") - if err != nil || !strings.HasPrefix(sql, "EXPLAIN ") { - t.Fatalf("%s: %v", dialect, err) - } - for _, invalid := range []string{"SELECT 1; SELECT 2", "DELETE FROM t", "EXPLAIN ANALYZE SELECT 1", "WITH x AS (DELETE FROM t RETURNING *) SELECT * FROM x", "SELECT 1 INTO OUTFILE '/tmp/x'"} { - if _, err := prepareExplainSQL(dialect, invalid); err == nil { - t.Fatalf("unsafe explain input accepted by %s: %s", dialect, invalid) + for _, dialect := range []string{"mysql", "mariadb", "postgresql", "clickhouse", "oceanbase-mysql", "oracle", "db2", "dameng", "sqlserver", "oceanbase-oracle"} { + t.Run(dialect, func(t *testing.T) { + v, _, c, _ := executionFixture(t, dialect, "", &executionDriverState{}) + for _, document := range []string{"SELECT 1", "SELECT 1; SELECT 2", "DELETE FROM t", "EXPLAIN ANALYZE SELECT 1", "WITH x AS (DELETE FROM t RETURNING *) SELECT * FROM x", "SELECT 1 INTO OUTFILE '/tmp/x'"} { + body, _ := json.Marshal(map[string]string{"sql": document}) + w := httptest.NewRecorder() + v.Handler().ServeHTTP(w, engineRequest("POST", "/chen/api/v1/consoles/"+c.id+"/explain", string(body), "token")) + allowed := document == "SELECT 1" && !specialPlanDialect(dialect) + if (w.Code == 200) != allowed { + t.Fatalf("explain acceptance: SQL=%s status=%d body=%s", document, w.Code, w.Body.String()) + } + if allowed { + var result struct { + SQL string `json:"sql"` + } + if err := json.Unmarshal(w.Body.Bytes(), &result); err != nil || !strings.HasPrefix(result.SQL, "EXPLAIN ") { + t.Fatalf("invalid estimated plan response: %s", w.Body.String()) + } + } } - } - } - for _, dialect := range []string{"oracle", "db2", "dameng", "sqlserver", "oceanbase-oracle"} { - if _, err := prepareExplainSQL(dialect, "SELECT 1"); err == nil { - t.Fatalf("unsupported plan accepted: %s", dialect) - } + }) } } diff --git a/internal/engine/query_page.go b/internal/engine/query_page.go index 4aa26f8..94e8564 100644 --- a/internal/engine/query_page.go +++ b/internal/engine/query_page.go @@ -196,9 +196,6 @@ func queryPageContext(ctx context.Context, c *console, d string) (string, error) b, _ := json.Marshal(values) return string(b), nil } -func (v *Server) captureQuerySource(s *session, c *console, e *execution, opts sqlservice.AnalyzeOptions) { - v.captureQuerySQLSource(s, c, e, opts, e.sql) -} func (v *Server) captureQuerySQLSource(s *session, c *console, e *execution, opts sqlservice.AnalyzeOptions, statementSQL string) { p := &queryPagePlan{original: statementSQL, options: opts, view: QueryPage{Mode: "cache", MaxPageSize: min(1000, v.opts.MaxRows), MaxOffset: browseMaxOffset}} e.querySource = p diff --git a/internal/engine/write_commands_live_test.go b/internal/engine/write_commands_live_test.go index b7be655..b4a6962 100644 --- a/internal/engine/write_commands_live_test.go +++ b/internal/engine/write_commands_live_test.go @@ -17,7 +17,7 @@ import ( // Explicit write tests only create uniquely named, disposable CHEN_W_* tables. // They never update existing tables. Their only DROP targets the exact object // successfully created by this test, and cleanup also runs on assertion failure. -// CHEN_WRITE_BACKENDS=/private/backends.yml go test -tags ibmdb ./internal/engine -run TestWriteCommandsLive -v +// CHEN_WRITE_BACKENDS=/private/backends.yml go test ./internal/engine -run TestWriteCommandsLive -v func TestWriteCommandsLive(t *testing.T) { path := os.Getenv("CHEN_WRITE_BACKENDS") if path == "" { diff --git a/internal/jumpserver/README.md b/internal/jumpserver/README.md deleted file mode 100644 index f0d63b1..0000000 --- a/internal/jumpserver/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# JumpServer integration contract - -The HTTP service accepts a Core connection token. `Open` exchanges it through -Wisp and returns a server-only connection configuration plus a Core audit session. -It never accepts a database address, password or DSN from the browser. - -The caller must: - -1. Start `WispProvider.Run` for outbox delivery, admin control and time limits. -2. Call `Open`, open/ping the database, then call `Connected`. On any failure call - `Close`. `Connected` records success only once and only after a real connection, - and transfers database-resource cleanup ownership to the caller even before - its Watch callback has registered. -3. Keep `Watch` attached to the logical session and cancel/close all database work - on `terminate`. The registered resource owner must stop/join database - cancellation before calling `Close`; this ordering keeps a gateway forward - alive long enough to send a server-side KILL. Watch returning does not transfer - that responsibility back to the provider. Stop `Run` only after the owner has - drained; Run shutdown supplies a final close fallback. Browser disconnection - does not end a database transaction. -4. Call `Grant.Check` before operations and result delivery, `Authorize` before - every statement, and `SetBusy` around database work. `Touch` must represent user - activity; polling and event subscriptions must not reset an idle timeout. -5. Audit executed and denied statements with `Record`. Preserve SQL outcome when - remote audit delivery fails. A local persistence error is `ErrAuditDisk` and - must be exposed as an audit failure separately from execution outcome. -6. Mask values before returning/caching results. Pass an empty source column if - the driver only supplies a result label. For sessions with field masking rules, - unknown source columns are conservatively masked to prevent alias bypass. - -## Policy compatibility - -- Core-provided ACL/group order is preserved. First match wins. -- Reject denies SQL before database execution. Warning executes with warning - audit risk. NotifyWarning requires explicit confirmation of immutable SQL via - `confirm_warnings`; this never bypasses rejection or review. Wisp notifies Core - only for SQL actually sent to the database. -- Review requires an explicit submission through `CommandAuthorizer`. Check the - entire script and every statement for rejection before submitting any ticket. - Bind tickets to the exact immutable batch, deduplicate matching reviews only - within that execution, and never cache approvals on a connection. Core stores - at most 4090 Unicode characters: longer reviewed batches are rejected, never - truncated. A pending ticket blocks SQL until approved; rejection, cancellation, - timeout and unknown/error responses fail closed. Waiting lasts at most 30 - minutes and rechecks session permission before execution. HTTP/SSE expose the - ticket link, reviewers and state, not Wisp's privileged check/cancel requests. -- Approval wait uses a separate bounded deadline. The engine suspends the SQL - budget during review and resumes its remaining budget afterward. Queueing and - database execution remain bounded. Mixed scripts retain their batch audit plus - warning records for distinct warning ACL/groups on SQL actually sent to the - database, so an approved command cannot suppress a warning notification. -- Ordinary ACL patterns use RE2. Java lookarounds and backreferences fall back - to regexp2 with a 100 ms match timeout; mixed Java literal quotation is - preserved. Invalid/unsupported syntax fails token authentication, and a match - timeout rejects the command before any later accepting rule. This does not - claim complete Java Pattern syntax compatibility. -- A face-monitor token registers through Wisp before a database connection is - allowed; failure compensates the Core session and fails closed. Non-password - database credentials explicitly require legacy Chen. Unknown admin actions lock - the session. -- Idle limits are minutes; maximum duration is hours. Running work suspends the - idle timer, but does not suspend permission expiry or maximum duration. -- Core dynamic permission expiry retains the established ten-minute grace. A - repeated event cannot extend it; a permission-valid event clears it. -- Losing Chen's Wisp task stream pauses existing sessions and rejects new token - exchanges until the stream reconnects. Recovery preserves independent admin - locks. Wisp v0.2.15 does not expose the health of its upstream Core WebSocket in - this protobuf contract, so a live Chen/Wisp stream does not prove Core control - connectivity. Wisp checks token validity every five minutes; this is not an - instantaneous authorization revocation guarantee. - -## Durable audit and cleanup - -The spool directory is required, checked at startup and restricted to mode 0700. -Entries contain audit SQL and bounded status summaries, use 0600, and are fsynced -before `Record` acknowledges acceptance. Do not put the spool on ephemeral storage -when audit durability across a container replacement is required. Raw result rows, -connect tokens, database passwords and TLS private keys are not stored there. - -Run retries command audit, lifecycle logs, replay upload, session finish, ticket cancellation and -forward cleanup after remote failure or restart. Successful cleanup steps are -checkpointed. UploadCommand has no idempotency key in Wisp v0.2.15, so lost replies may produce duplicate audit -records. The outbox never re-executes SQL. - -Each session records asciinema v2 SQL input and the same redacted bounded status -summary as command audit; raw query result rows are excluded. Replay upload is -acknowledged before local files are deleted and before session finish. The durable -canonical cast regenerates a compressed file after a lost Wisp response; startup -recovers unfinished local casts without executing SQL. - -Canceling a pending review durably queues Core ticket closure. A bounded detached -observer also closes a ticket returned after the requesting execution has been -canceled. Wisp's CreateCommandTicket has no idempotency key: an entirely lost -creation response leaves no ticket ID to compensate, so creation is not retried. -Known cancel requests remain in the outbox across restart until delivered. - -Core sessions use a generated UUID supplied to CreateSession. This enables cleanup -after an unknown CreateSession RPC outcome. Browser cancellation during a forward -creation does not abandon the RPC result: a bounded detached observer queues late -returned IDs for deletion. Wisp's CreateForward has no request idempotency key; -when its remote response is lost entirely, an unreturned forward ID cannot be -recovered by this client. A future Wisp idempotency change is needed to remove -that protocol limitation completely. - -## Verification boundary - -Unit/contract tests exercise a generated gRPC client/server round trip using -in-memory transport, including approval states, exact SQL binding, late ticket -cleanup, control-stream loss/recovery and independent admin locks, plus injected -auth, audit, disk, resource-cleanup and cancellation failures. - -`internal/engine/managed_live_integration_test.go` is separately opt-in through -`CHEN_MANAGED_FIXTURE`. It requires a private fixture for an isolated loopback -Core/Wisp with PostgreSQL, dedicated ordinary operator/reviewer accounts, the -`chen_rejected`, `chen_review`, `chen_warning` policies and a local notification -sink and a running Celery worker. It creates fresh sessions/tickets and only -sends synthetic SELECT SQL. Temporary masking/ACL rules are scoped to the fixture -operator and asset and deleted afterward; permission changes are restored. -It checks Core's persisted audit and session state separately from Chen's local -`recorded` acknowledgement; Wisp asynchronously batches uploads. It does not -exercise production, external mail delivery or gateways. - -On 2026-10-26 the isolated Core + Wisp v0.2.15 + PostgreSQL run passed against the latest local dev checkout. Its acceptance coverage is: - -| Interaction | Evidence boundary | -| --- | --- | -| Token login and permissions | Real Core token exchange, PostgreSQL connection, copy/paste/transfer flags, denial of new tokens after permission removal | -| SQL policy and approval | Real reject, explicit submit, exact batch inspection, approve/reject/cancel/timeout, admin lock during review, canceled Core ticket | -| Warning and auditing | Explicit NotifyWarning confirmation and persisted Core command records and reviewer site messages; a mixed approved batch still generates its warning; parse failure and an unsent warning statement do not | -| Admin lock/unlock/kill | Real Core task delivery, locked query refusal, query after unlock, revoked Chen session token and Core session completion after kill | -| Replay | Core marks sessions as having replay, stores nonempty gzip files, and the files reopen as valid asciinema v2 | -| Component heartbeat | Core reports a newly changed heartbeat Status UUID, alive component and online sessions while the test connection stays open | -| Data masking | Real Core rule distribution and masked synthetic text, NULL and unknown-origin alias results before HTTP delivery | -| Dynamic permission change | `CHEN_MANAGED_REVOCATION=1` separately waits up to six minutes for Wisp's real five-minute check and observes the revocation control; the ten-minute expiry grace is retained | - -Live gateway forwarding/TLS, face monitoring, non-password credentials, external -mail delivery, Core outage recovery and the full ten-minute revocation deadline -were not deployment-tested in this run. Control-stream interruption/recovery, -cleanup retries and expiry timing have contract-test coverage. Clipboard flags -describe the client interaction policy; they are not a guarantee against copying -data already delivered to an authorized browser. diff --git a/internal/sqlservice/README.md b/internal/sqlservice/README.md deleted file mode 100644 index 826e49a..0000000 --- a/internal/sqlservice/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# SQL language service - -The execution parser and editor completion have separate responsibilities. - -* `AnalyzeWithOptions` parses the complete script with the pinned TiDB parser for - MySQL, or PostgreSQL 17's grammar through `pg_query_go/v6`. It returns original - UTF-8 byte spans, result-set hints and advisory write classification. Statements - are never reserialized before execution. PostgreSQL modifying CTEs are inspected - recursively. These classifications do **not** prove absence of side effects - (functions, triggers, stored procedures and extensions may perform writes). -* Oracle and SQL Server use pinned native parsers, including administrative DDL - and procedural/control statements. Nested statements retain source spans for - individual ACL checks. Oracle-compatible procedural documents on Dameng and - OceanBase Oracle use the Oracle grammar for boundaries; their server remains - authoritative for product syntax. Routine definitions in T-SQL must occupy - their own execution so trailing body statements cannot be sent separately. -* Db2, Dameng, ClickHouse and OceanBase Oracle use a bounded command scanner for - common queries, CRUD, DDL, privilege management, maintenance and transactions. - It checks quote/comment termination, parentheses and statement boundaries; - the **database server validates vendor grammar**. MariaDB and OceanBase MySQL - use the MySQL parser first with this fallback for dialect extensions. Ambiguous - implicit batches and procedural syntax without a proven boundary are rejected. - Original SQL is never rewritten. -* Automatic read replay retains a stricter SELECT classifier and live source - verification. Explicitly confirmed database paging/count/export additionally - admits views and server-resolved functions. Handwritten WHERE expressions may - use functions and read subqueries after confirmation but cannot escape their - wrapper, add writes, locks or statement tails. Confirmation is bound to the - original result and connection context, and does not bypass ACL/audit checks. -* `Completion` accepts a Monaco UTF-16 cursor position and document version. It - returns a full-token replacement range and insertion text with dialect-correct - identifier quoting. An error-tolerant lexer and SELECT scope grammar keep - comments, quoted strings, aliases, nested queries, CTEs and derived projections - separate. This path does not execute or prepare the user's SQL. -* `Metadata` and `Columns` issue only parameterized catalog queries on the supplied - authenticated connection. PostgreSQL unqualified names resolve against that - connection's current `search_path`. Catalog operations have a three-second - deadline. Completion returns at most 200 candidates, rejects nesting beyond 128 - levels, and accepts documents up to 2 MiB. - -The caller must serialize access to each `sql.Conn`. Do not run completion on a -connection already occupied by a query. An independent catalog connection must -carry the same database/schema/search-path context and account permissions. No -global or cross-session catalog cache exists here; only a single completion -request memoizes repeated column lookups. The HTTP/session layer supplies the live -MySQL SQL mode and numeric version through `AnalyzeOptions` and -`CompletionRequest.Options`; this field cannot be set by JSON input. Refresh it -after session settings change. - -## Explicit V1 limits - -The TiDB grammar is not identical to MySQL. The pinned version accepts stored -procedure bodies but rejects the `DELIMITER` client directive, MariaDB's -`INSERT ... RETURNING` (handled by the bounded fallback), and MySQL's `SELECT ... INTO @variable`. Unsupported -execution syntax returns an error; it is never silently split or rewritten. -MySQL versioned executable comments are masked **only in parser input** according -to the supplied server version, preserving original source offsets and SQL. - -PostgreSQL parsing uses a version-17 grammar; parsing a feature does not establish -server-14/16 compatibility. PL/pgSQL function bodies are additionally syntax -checked, but server objects, types, privileges, extension languages and runtime -semantics still require server validation. A parser verdict is not a permission -check. - -Completion currently covers common SELECT/JOIN contexts, table/schema prefixes, -columns, alias shadowing, correlated subqueries, CTE explicit columns and projected -columns, derived tables, wildcard projection, common read-query keywords and basic -built-in function signatures. FROM/JOIN/comma relation positions query only table -candidates; expression positions query only referenced relation columns. Ambiguous -columns insert a qualified alias, JOIN ON limits visibility to preceding relations, -and USING offers only shared columns not already listed. ORDER/GROUP BY also offer -explicit projection aliases. Unknown alias-dot expressions return no invented -schema-table candidates. UNION/INTERSECT/EXCEPT branch aliases are isolated. -Unqualified PostgreSQL tables honor live search_path visibility, including same-name -relations in multiple schemas. Candidate replacement ranges cover the complete -identifier token; optional filter_text/sort_text preserve raw-name matching and -server ranking in Monaco. Completion never infers JOIN equality without foreign-key -metadata; foreign-key relationship suggestions remain a future extension. It deliberately omits stored-program variables, dynamic SQL, -table-valued function output types, inferred expression types, exhaustive dialect -function overloads and full set-operation result/order-by semantics. Complex unsupported -editor contexts may yield fewer candidates. A document version in the response -lets the client discard stale replies. - -Tests include whole-document syntax failures, original source spans, dollar -quotes, nested comments, stored routines, SQL modes and versioned comments; -completion tests exercise CTE/derived projections, shadowing, Unicode/UTF-16, -quoted identifiers and isolation; catalog-driver tests verify parameterization, -bounded limits and cancellation. They are not a substitute for live MySQL and -PostgreSQL compatibility tests. - -## Additional identifier and catalog dialects - -Oracle, Db2 and Dameng fold unquoted identifiers to uppercase and preserve quoted -case; SQL Server recognizes escaped bracket identifiers; ClickHouse preserves -case. Oracle q/NQ literals (including multibyte delimiters), SQL Server TOP -modifiers and Oracle MINUS branches remain isolated in completion. Catalogs use -the current authenticated connection and each engine's visibility rules. Parameter -markers follow the driver, including OceanBase Oracle's `?` wire markers. - -Real-instance tests are opt-in via `TestReadonlyDatabaseMatrix`. They only read -catalog metadata, use `WHERE 1=0` for object identifier validation, and return -synthetic constants for long Unicode text, exact integers/JSON and binary values. -They do not create database objects or read application rows. Mock tests alone do -not establish server-version compatibility; see `docs/database-compatibility.md`. diff --git a/internal/sqlservice/analyze.go b/internal/sqlservice/analyze.go index 3467b00..e994400 100644 --- a/internal/sqlservice/analyze.go +++ b/internal/sqlservice/analyze.go @@ -278,11 +278,6 @@ func pgStatementCapabilities(node *pg.Node) (bool, bool) { return rows, write } -func Split(dialect Dialect, document string) ([]Statement, error) { - a, e := Analyze(dialect, document) - return a.Statements, e -} - // TiDB recognizes every versioned comment. Masking only in the parser input // aligns its behavior with the server version while retaining byte positions. // Strings and normal comments are skipped by the editor lexer. diff --git a/internal/sqlservice/catalog_overview_extra.go b/internal/sqlservice/catalog_overview_extra.go index 0b7c9cb..01817fb 100644 --- a/internal/sqlservice/catalog_overview_extra.go +++ b/internal/sqlservice/catalog_overview_extra.go @@ -75,10 +75,6 @@ func overviewExtraSource(d Dialect, b *catalogBinder, database string, broadSegm return `SELECT o.dbname,o.schemaname,o.objectname,o.objectkind,o.objecttype,o.objectcomment,` + fields + `,` + properties + ` FROM (` + base + `) o` + join, nil } -func overviewExtraSQL(d Dialect, r OverviewRequest, broadSegments ...bool) (catalogQuery, error) { - return overviewExtraSQLWithClickHouse(d, r, nil, broadSegments...) -} - func overviewExtraSQLWithClickHouse(d Dialect, r OverviewRequest, clickHouse *clickHouseCapabilities, broadSegments ...bool) (catalogQuery, error) { switch r.Section { case "tables", "views", "indexes", "statistics", "properties": diff --git a/internal/sqlservice/query_page_grammar.go b/internal/sqlservice/query_page_grammar.go index 240615b..2eca7d4 100644 --- a/internal/sqlservice/query_page_grammar.go +++ b/internal/sqlservice/query_page_grammar.go @@ -115,8 +115,6 @@ func (p *repeatableQueryParser) readAlias() (string, bool) { } return "", true } -func (p *repeatableQueryParser) alias() bool { _, ok := p.readAlias(); return ok } - func (p *repeatableQueryParser) boundary() bool { for _, s := range []string{"FROM", "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", "OFFSET", "FETCH", "UNION", "INTERSECT", "EXCEPT", "MINUS", "JOIN", "INNER", "LEFT", "RIGHT", "FULL", "CROSS", "OUTER", "ON", "USING", "AND", "OR", "WHEN", "THEN", "ELSE", "END", "ROWS", "RANGE", "PRECEDING", "FOLLOWING", "ASC", "DESC", "NULLS"} { if p.peek(s) { diff --git a/scripts/package-image.sh b/scripts/package-image.sh index 55803b6..3465558 100755 --- a/scripts/package-image.sh +++ b/scripts/package-image.sh @@ -18,7 +18,6 @@ docker cp "$container:/opt/chen/entrypoint.sh" "$staging/$name/entrypoint.sh" docker cp "$container:/opt/chen/conf" "$staging/$name/conf" docker cp "$container:/opt/chen/licenses" "$staging/$name/licenses" cp "$root/LICENSE" "$root/README.md" "$staging/$name/" -cp "$root/docs/build.md" "$staging/$name/BUILD.md" cat > "$staging/$name/start.sh" <<'START' #!/usr/bin/env bash set -euo pipefail