diff --git a/.claude/skills/setup/SKILL.md b/.claude/skills/setup/SKILL.md new file mode 100644 index 000000000..8063ae094 --- /dev/null +++ b/.claude/skills/setup/SKILL.md @@ -0,0 +1,74 @@ +--- +name: setup +description: > + Set up the `clickhouse-js` repository in a fresh checkout so the agent can run + tests, lints, type checks, builds, or examples. Use this skill before invoking + any `npm run test:*`, `npm run lint`, `npm run typecheck`, `npm run build`, or + `npm run run-examples` script — or after pulling changes that touch any + `package.json` (root, `examples/node`, or `examples/web`). Covers Node.js + version requirements, installing dependencies across the npm workspaces and + the two independent example packages, building the workspace packages so + inter-package imports resolve, and starting ClickHouse via Docker Compose for + integration tests. Do NOT use this skill for downstream user projects that + merely depend on `@clickhouse/client` or `@clickhouse/client-web`; it is + specific to contributing to the `ClickHouse/clickhouse-js` repo itself. +--- + +# clickhouse-js Repository Setup + +Use this skill before running any of the `npm run test:*`, `npm run lint`, `npm run typecheck`, or `npm run build` scripts in a fresh checkout (or after pulling changes that touch `package.json` files). + +## Prerequisites + +- **Node.js 22 recommended** (matches `.nvmrc`). The root `package.json` declares `"engines": { "node": ">=20.19.0" }`, and CI tests Node 20, 22, and 24. +- **Docker** with the Compose plugin (`docker compose ...`). Required only for integration tests and any example that talks to a real server. + +## 1. Install dependencies + +This is an npm workspaces repo (`packages/*`), with two additional independent example packages (`examples/node`, `examples/web`) that have their own `package.json` and are **not** part of the workspaces. + +Install all three: + +```bash +npm install +npm --prefix examples/node install +npm --prefix examples/web install +``` + +The root `postinstall` script patches `node_modules/parquet-wasm/package.json`; it runs automatically as part of `npm install`. + +## 2. Build the workspace packages + +The workspace packages (`@clickhouse/client-common`, `@clickhouse/client`, `@clickhouse/client-web`) must be built before some tests, examples, and typechecks can resolve their inter-package imports: + +```bash +npm run build +``` + +This runs `build` in every workspace package. + +## 3. Start ClickHouse (only for integration tests / examples) + +Unit tests do **not** need a server. Integration tests (`npm run test:*:integration*`) and the example runners do. + +From the repo root: + +```bash +docker compose up -d +``` + +This starts both the single-node setup (`clickhouse` on 8123/9000, `clickhouse_tls` on 8443/9440) and the two-node cluster (`clickhouse1`, `clickhouse2`, plus the `nginx` round-robin entrypoint on 8127). All services use non-overlapping ports so a single `up -d` covers every integration test mode. + +To override the server version, set `CLICKHOUSE_VERSION` when starting Compose; for example: `CLICKHOUSE_VERSION=head docker compose up -d`, `CLICKHOUSE_VERSION=latest docker compose up -d`, or `CLICKHOUSE_VERSION=24.8 docker compose up -d` to use an explicit version tag. + +## 4. Verify + +After the steps above you can run, for example: + +- `npm run lint` — lint every workspace package +- `npm run typecheck` — typecheck every workspace package +- `npm run test:node:unit` / `npm run test:web:unit` — unit tests, no server required +- `npm run test:node:integration` / `npm run test:web:integration` — integration tests, server required +- From `examples/node` or `examples/web`: `npm run lint`, `npm run typecheck`, `npm run run-examples` + +See `npm run` from the repo root for the full list of test scripts. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 636ac6f6e..c272e9631 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -18,10 +18,6 @@ updates: dev-dependencies: dependency-type: 'development' ignore: - - dependency-name: 'eslint' - versions: ['>= 10'] - - dependency-name: '@eslint/js' - versions: ['>= 10'] - dependency-name: '@opentelemetry/auto-instrumentations-node' versions: ['0.70.0'] - dependency-name: '@types/node' diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml deleted file mode 100644 index 83a5a5476..000000000 --- a/.github/workflows/copilot-setup-steps.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: 'Copilot Setup Steps' - -# Set the permissions to the lowest permissions possible needed for your steps. -# Copilot will be given its own token for its operations. -permissions: - # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. - # If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. - contents: read - -# Automatically run the setup steps when they are changed to allow for easy validation, and -# allow manual testing through the repository's "Actions" tab -on: - workflow_dispatch: - push: - paths: - - .github/workflows/copilot-setup-steps.yml - pull_request: - paths: - - .github/workflows/copilot-setup-steps.yml - -jobs: - # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. - copilot-setup-steps: - runs-on: ubuntu-latest - - # You can define any steps you want, and they will run before the agent starts. - # If you do not check out your code, Copilot will do this for you. - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - - name: Setup NodeJS - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: 24 - - - name: Install dependencies (Node examples) - working-directory: examples/node - run: | - npm install || echo "Failed to install dependencies in examples/node" - - - name: Install dependencies (Web examples) - working-directory: examples/web - run: | - npm install || echo "Failed to install dependencies in examples/web" - - - name: Install dependencies - run: | - npm install || echo "Failed to install dependencies" - - - name: Build packages - run: | - npm run build || echo "Failed to build packages" - - - name: Start ClickHouse - uses: isbang/compose-action@3846bcd61da338e9eaaf83e7ed0234a12b099b72 # v2.4.2 - env: - CLICKHOUSE_VERSION: 'latest' - with: - compose-file: 'docker-compose.yml' - down-flags: '--volumes' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6f6c5c3be..39ff21382 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -72,6 +72,9 @@ jobs: latest: if: github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest + permissions: + contents: write # Required to push the release git tag + id-token: write # Required for npm OIDC authentication and provenance steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -86,9 +89,11 @@ jobs: run: npm ci - name: Get the release version + id: version run: | BASE_VERSION=$(node -p "require('./packages/client-common/package.json').version") echo "Using version: $BASE_VERSION" + echo "version=$BASE_VERSION" >> "$GITHUB_OUTPUT" - name: Build packages run: npm --workspaces run build @@ -98,3 +103,16 @@ jobs: npm --workspaces publish \ --access public \ --provenance + + - name: Create and push release git tag + env: + RELEASE_VERSION: ${{ steps.version.outputs.version }} + run: | + if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then + echo "Tag ${RELEASE_VERSION} already exists on origin; skipping." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "${RELEASE_VERSION}" -m "Release ${RELEASE_VERSION}" + git push origin "refs/tags/${RELEASE_VERSION}" diff --git a/.github/workflows/upstream-sql-tests.yml b/.github/workflows/upstream-sql-tests.yml new file mode 100644 index 000000000..0d7446a04 --- /dev/null +++ b/.github/workflows/upstream-sql-tests.yml @@ -0,0 +1,118 @@ +name: 'upstream-sql-tests' + +permissions: {} + +on: + workflow_dispatch: + inputs: + upstream_ref: + description: 'ClickHouse/ClickHouse ref to check out' + required: false + default: 'master' + type: string + schedule: + - cron: '0 5 * * *' + push: + branches: + - main + paths: + - 'tests/clickhouse-test-runner/**' + - '.github/workflows/upstream-sql-tests.yml' + pull_request: + paths: + - 'tests/clickhouse-test-runner/**' + - '.github/workflows/upstream-sql-tests.yml' + +concurrency: + group: '${{ github.workflow }}-${{ github.ref }}' + cancel-in-progress: true + +env: + UPSTREAM_REPO: 'ClickHouse/ClickHouse' + +jobs: + upstream-sql-tests: + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + clickhouse: [head, latest] + # Round-robin shards keep each job at roughly one minute so the + # upstream SQL tests no longer dominate PR CI runtime. Bump + # `shard` and `SHARD_TOTAL` together if the allowlist grows enough + # that per-shard runtime climbs back above ~1 minute. + shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + steps: + - name: Checkout clickhouse-js + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Checkout ClickHouse upstream + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: ${{ env.UPSTREAM_REPO }} + ref: ${{ github.event.inputs.upstream_ref || 'master' }} + path: tests/clickhouse-test-runner/.upstream/ClickHouse + sparse-checkout: | + tests/clickhouse-test + tests/queries + tests/config + tests/ci + tests/performance + docker/test/util + fetch-depth: 1 + + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 24 + + - name: Setup Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.12' + + - name: Install Python dependencies for upstream clickhouse-test + run: | + python -m pip install --upgrade pip + pip install jinja2 + + - name: Start ClickHouse (version - ${{ matrix.clickhouse }}) in Docker + uses: isbang/compose-action@3846bcd61da338e9eaaf83e7ed0234a12b099b72 # v2.4.2 + env: + CLICKHOUSE_VERSION: ${{ matrix.clickhouse }} + with: + compose-file: 'docker-compose.yml' + down-flags: '--volumes' + + - name: Build test runner + working-directory: tests/clickhouse-test-runner + run: | + npm install + npm run build + + - name: Make upstream test script executable + run: | + chmod +x tests/clickhouse-test-runner/.upstream/ClickHouse/tests/clickhouse-test + + - name: Run upstream SQL tests + id: run-tests + env: + CLICKHOUSE_CLIENT_CLI_LOG: ${{ github.workspace }}/upstream-run.log + SHARD_INDEX: ${{ matrix.shard }} + SHARD_TOTAL: 10 + run: | + bash tests/clickhouse-test-runner/scripts/run-upstream-tests.sh --no-stateful + + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: upstream-sql-tests-${{ matrix.clickhouse }}-shard-${{ matrix.shard }} + retention-days: 14 + if-no-files-found: ignore + path: | + upstream-run.log + tests/clickhouse-test-runner/.upstream/ClickHouse/tests/queries/**/*.stdout + tests/clickhouse-test-runner/.upstream/ClickHouse/tests/queries/**/*.stderr + tests/clickhouse-test-runner/.upstream/ClickHouse/tests/queries/**/*.diff diff --git a/AGENTS.md b/AGENTS.md index 0bc76ebdc..12b10388a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,13 +12,13 @@ } ``` -2. When adding new log messages with suggestions for users, make sure to create a unique documentation page in the `docs` directory with a detailed explanation of the issue and how to resolve it. Then, include a link to that documentation page in the log message. For example: +2. When adding new log messages with suggestions for users, make sure to create a unique documentation page under the `docs/` directory (use `docs/howto/` for task-style guides; see `docs/socket_hang_up_econnreset.md` as a reference) with a detailed explanation of the issue and how to resolve it. Then, include a link to that documentation page in the log message. For example: ```ts if (some_condition) { log_writer.warn({ message: - 'Example log message with suggestions for users. For more information, see https://github.com/ClickHouse/clickhouse-js/blob/main/docs/example-log-message.md', + 'Example log message with suggestions for users. For more information, see https://github.com/ClickHouse/clickhouse-js/blob/main/docs/socket_hang_up_econnreset.md', }) } ``` @@ -49,7 +49,8 @@ The goals of the refactor are: - `coding/` — day-to-day client API usage (configure, ping, basic insert/select, parameter binding, sessions, data types, custom JSON). - `performance/` — async inserts, streaming with backpressure, file/Parquet streams, progress - streaming, server-side bulk moves. Node-only (no `performance/` folder under `examples/web`). + streaming, server-side bulk moves. Mostly Node-only; `examples/web/performance/` exists for the + few perf scenarios that work in the browser (e.g. streaming `JSONEachRow`). - `troubleshooting/` — cancellation, timeouts, long-running query progress, server error surfaces, number-precision pitfalls. - `security/` — TLS, RBAC, SQL-injection-safe parameter binding. @@ -61,6 +62,42 @@ The goals of the refactor are: a duplicated example, update **all** copies. The current duplicates and their primary locations are listed in [`examples/README.md`](examples/README.md#editing-duplicated-examples). +## Skills + +- Each shipped skill must also be listed in the `agents.skills` array of + [`packages/client-node/package.json`](packages/client-node/package.json) so downstream tooling can + discover it. The [`Skills E2E`](.github/workflows/e2e-skills.yml) workflow + (`tests/e2e/skills/check.js`) asserts that the packaged tarball contains the declared skills. + +## Embedded docs + +The [`docs/`](docs) directory holds long-form troubleshooting / how-to pages that log messages and +skill references can link to (e.g. `docs/socket_hang_up_econnreset.md`, `docs/howto/`). Prefer +adding new pages here over linking out to external docs from log messages. + +## Upstream SQL test harness + +The [`tests/clickhouse-test-runner`](tests/clickhouse-test-runner) harness is a Node.js port of `clickhouse-client` that allows the official ClickHouse Python test runner (`tests/clickhouse-test`) to drive a subset of the upstream SQL test suite against `@clickhouse/client`. + +### What the harness does + +- Wraps `@clickhouse/client` in a tiny CLI (`bin/clickhouse` → `dist/main.js`) that mimics enough of the upstream `clickhouse-client` binary (same flags, `extract-from-config` shortcut, stdin/`--query` behavior) for the Python `tests/clickhouse-test` runner to drive it without modification. +- The CI matrix runs the harness against ClickHouse `latest` and `head` so that we exercise `@clickhouse/client` against both server versions and detect server regressions. The allowlist is also split into round-robin shards (`SHARD_INDEX` / `SHARD_TOTAL`) so each matrix job stays at roughly one minute; bump both the `shard` matrix values and the `SHARD_TOTAL` env value in the workflow together if per-shard runtime climbs back above ~1 minute. +- Reads the curated test list from [`upstream-allowlist.txt`](tests/clickhouse-test-runner/upstream-allowlist.txt) (one test name per line, `#` for comments) and forwards them as positional arguments to `tests/clickhouse-test`. +- The `SERVER_SETTINGS`/`CLIENT_ONLY_SETTINGS` allowlists in [`src/settings.ts`](tests/clickhouse-test-runner/src/settings.ts) are copied from the Java port and may need periodic resync as ClickHouse adds or reclassifies settings. + +See [`tests/clickhouse-test-runner/README.md`](tests/clickhouse-test-runner/README.md) for build, usage, and environment-variable documentation. When harness behavior changes (new wrapper flags, new short-circuited keys in `bin/clickhouse`, new entries in the settings allowlists), review the README and [`.github/workflows/upstream-sql-tests.yml`](.github/workflows/upstream-sql-tests.yml) to keep them in sync with the implementation. + +### Strategy for growing the allowlist + +The allowlist is grown in **batches of ~100 candidate tests at a time**, in upstream filename order, following this loop: + +1. **Pre-filter the candidate batch.** Skip non-SQL tests (`.sh`, `.py`, `.j2`) and tests tagged for unsupported infrastructure (`shard`, `distributed`, `replicated`, `zookeeper`, `kafka`, `s3`, `mysql`, `tls`, etc.). These will never pass through this harness as it stands today. +2. **Run each candidate through the harness** with `--no-stateful --no-long`. **Only keep tests that report `[ OK ]`**; drop failures and skips. +3. **Validate against the CI matrix before committing**, not just one local server version. The CI workflow runs `{ClickHouse latest, head} × {shard 1..N}` — a test that passes locally on `head` may fail on `latest` (or vice versa) and break CI. +4. **Beware substring/prefix expansion.** `tests/clickhouse-test` treats positional arguments as **substring/prefix matches** rather than exact names, so an allowlist entry like `00396_uuid` will silently pull in `00396_uuid_v7`, `00712_prewhere_with_alias` will pull in `00712_prewhere_with_alias_bug_2`, etc. When adding an entry whose name is a prefix of any other test in `0_stateless`, prefer the longest unambiguous form, or accept that the siblings come along and verify they all pass. +5. **Prune flakes promptly.** If a previously-passing test starts to flake on the nightly run, remove it (or its prefix-expanded siblings) from the allowlist rather than retrying — the allowlist exists to be a stable green signal, not a TODO list. + ## When reviewing code changes For every pull request review, make sure to provide an evaluation of the following aspects: @@ -75,6 +112,6 @@ For every pull request review, make sure to provide an evaluation of the followi 1. When reviewing code changes, it is important to consider the impact on the API quality and stability. For example, if the code changes involve modifying the library's public API surface (such as exported functions, classes, or types) or adding new public APIs, it is important to ensure that the changes are well-documented and do not break existing functionality for users of the library. -2. When introducing new features or making changes to the API make sure to update the CHANGELOG.md file with a concise description of the changes followed with an example usage if applicable. +2. When introducing new features or making changes to the API, make sure the PR description includes a concise, human-readable CHANGELOG entry (followed by an example usage if applicable) so it can be folded into `CHANGELOG.md` at release time. This matches the PR template checklist item ("A human-readable description of the changes was provided to include in CHANGELOG"). 3. Additionally, make sure that the official documentation is in sync with the changes. diff --git a/CHANGELOG.md b/CHANGELOG.md index 235f57d50..ce64c07a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# 1.18.5 + +## Improvements + +- (Node.js only) Added `max_response_headers_size` client option that forwards the [`maxHeaderSize`](https://nodejs.org/api/http.html#httprequesturl-options-callback) option to the underlying `http(s).request` call. This raises the per-request limit on the total size of HTTP response headers received from the server (Node.js default is ~16 KB). It is most useful when running long-running queries with `send_progress_in_http_headers` enabled — the `X-ClickHouse-Progress` headers accumulate over the lifetime of the request and can exceed the default limit, causing the request to fail with `HPE_HEADER_OVERFLOW`. Setting this option avoids the need to use the global `--max-http-header-size` Node.js CLI flag or the `NODE_OPTIONS` environment variable. Has no effect for the Web client (which uses `fetch`) and no effect when a custom `http_agent` is configured with a request implementation that does not honor the option. + +```ts +const client = createClient({ + request_timeout: 400_000, + max_response_headers_size: 1024 * 1024, // accept up to 1 MiB of response headers + clickhouse_settings: { + send_progress_in_http_headers: 1, + http_headers_progress_interval_ms: '110000', + }, +}) +``` + +- The `@clickhouse/client` npm package now ships embedded AI-agent skills, `clickhouse-js-node-coding` and `clickhouse-js-node-troubleshooting`, under `node_modules/@clickhouse/client/skills/`. These skills are also declared in the `agents.skills` field of the package manifest for discovery tools that scan `node_modules`. This allows agentic coding tools to load focused, Node-client-specific coding and troubleshooting guidance without any additional setup. ([#682]) + +[#682]: https://github.com/ClickHouse/clickhouse-js/pull/682 + +# 1.18.4 + +A release-infrastructure-only version bump (no user-facing changes). See 1.18.5 for the next release with user-facing improvements. + # 1.18.3 ## Improvements diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6e78c842..781c97516 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -203,3 +203,7 @@ The average reported test coverage is above 90%. We generally aim towards this t Currently, automatic coverage reports are disabled. See [#177](https://github.com/ClickHouse/clickhouse-js/issues/177), as it should be restored in the scope of that issue. + +## Running upstream ClickHouse SQL tests + +The [`tests/clickhouse-test-runner`](tests/clickhouse-test-runner) directory contains a Node.js port of `clickhouse-client` that lets `tests/clickhouse-test` from `ClickHouse/ClickHouse` exercise the JS client against the upstream SQL test suite. This harness helps validate that `@clickhouse/client` behaves correctly against real ClickHouse tests. See the [clickhouse-test-runner README](tests/clickhouse-test-runner/README.md) for setup and usage instructions. diff --git a/docs/howto/long_running_queries.md b/docs/howto/long_running_queries.md index 070b2c16a..7351dbfdc 100644 --- a/docs/howto/long_running_queries.md +++ b/docs/howto/long_running_queries.md @@ -40,6 +40,21 @@ curl -v "http://localhost:8123/?wait_end_of_query=1&send_progress_in_http_header The default value for `http_headers_progress_interval_ms` is defined by how often ClickHouse sends progress updates for the query type. For some queries, this may be too frequent (causing unnecessary overhead and/or HTTP client headers buffer overflow) or too infrequent (failing to keep the LB connection alive). Therefore, it's recommended to set it explicitly when using `send_progress_in_http_headers`. +> **Note (Node.js):** Node.js caps the total size of received HTTP headers at ~16 KB by default. Each `X-ClickHouse-Progress` header is roughly 200 bytes, so after ~75 progress headers accumulate the request fails with `HPE_HEADER_OVERFLOW`. Since `>= 1.18.5`, you can raise this limit per client (without resorting to the global `--max-http-header-size` CLI flag or `NODE_OPTIONS`) by passing `max_response_headers_size` (in bytes) to `createClient`: +> +> ```ts +> const client = createClient({ +> request_timeout: 400_000, +> max_response_headers_size: 1024 * 1024, // 1 MiB +> clickhouse_settings: { +> send_progress_in_http_headers: 1, +> http_headers_progress_interval_ms: '110000', +> }, +> }) +> ``` +> +> The Web client uses `fetch` and is not subject to this limit. + **Step 1.** Estimate the maximum query execution time. Set `request_timeout` to a value safely above that estimate. **Step 2.** Find out your LB's idle connection timeout (e.g. 120s). Set `http_headers_progress_interval_ms` to a value a few seconds below it (e.g. `'110000'`). diff --git a/package-lock.json b/package-lock.json index 32f6720f6..395e06c5c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "./packages/*" ], "devDependencies": { - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@faker-js/faker": "^10.3.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.71.0", @@ -29,7 +29,7 @@ "@vitest/coverage-istanbul": "^4.1.0", "@vitest/coverage-v8": "^4.1.0", "apache-arrow": "^21.0.0", - "eslint": "^9.39.4", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-expect-type": "^0.6.2", "eslint-plugin-prettier": "^5.5.5", @@ -822,153 +822,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.4.tgz", + "integrity": "sha512-lf19F24LSMfF8weXvW5QEtnLqW70u7kgit5e9PSx0MsHAFclGd1T9ynvWEMDT1w5J4Qt54tomGeAhdoAku1Xow==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.4", "debug": "^4.3.1", - "minimatch": "^3.1.5" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.4.tgz", + "integrity": "sha512-jJhqiY3wPMlWWO3370M86CPJ7pt8GmEwSLglMfQhjXal07RCvhmU0as4IuUEW5SJeunfItiEetHmSxCCe9lDBg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.0.tgz", + "integrity": "sha512-8FTGbNzTvmSlc4cZBaShkC6YvFMG0riksYWRFKXztqVdXaQbcZLXlFbSpC05s70sGEsXAw0qwhx69JiW7hQS7A==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.4.tgz", + "integrity": "sha512-55lO/7+Yp0ISKRP0PsPtNTeNGapXaO085aELZmWCVc5SH3jfrqpuU6YgOdIxMS99ZHkQN1cXKE+cdIqwww9ptw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.0.tgz", + "integrity": "sha512-ejvBr8MQCbVsWNZnCwDXjUKq40MDmHalq7cJ6e9s/qzTUFIIo/afzt1Vui9T97FM/V/pN4YsFVoed5NIa96RDg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.0", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@faker-js/faker": { @@ -3784,6 +3720,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -4146,19 +4089,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@vitest/browser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-4.1.0.tgz", @@ -4376,9 +4306,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -4511,13 +4441,6 @@ "undici-types": "~7.16.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/array-back": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", @@ -4557,13 +4480,6 @@ "dev": true, "license": "MIT" }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/baseline-browser-mapping": { "version": "2.10.8", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", @@ -4651,16 +4567,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001779", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", @@ -4897,13 +4803,6 @@ "node": ">=20" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5071,33 +4970,30 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -5107,8 +5003,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -5116,7 +5011,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -5198,72 +5093,50 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5609,19 +5482,6 @@ "node": ">=10.13.0" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/google-logging-utils": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", @@ -5696,23 +5556,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -5815,19 +5658,6 @@ "dev": true, "license": "MIT" }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6147,13 +5977,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -6502,19 +6325,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parquet-wasm": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/parquet-wasm/-/parquet-wasm-0.7.1.tgz", @@ -6833,16 +6643,6 @@ "node": ">=9.3.0 || >=8.10.0 <9.0.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -7174,19 +6974,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7850,16 +7637,16 @@ }, "packages/client-common": { "name": "@clickhouse/client-common", - "version": "1.18.4", + "version": "1.18.5", "license": "Apache-2.0", "devDependencies": {} }, "packages/client-node": { "name": "@clickhouse/client", - "version": "1.18.4", + "version": "1.18.5", "license": "Apache-2.0", "dependencies": { - "@clickhouse/client-common": "1.18.4" + "@clickhouse/client-common": "1.18.5" }, "devDependencies": { "simdjson": "^0.9.2" @@ -7870,10 +7657,10 @@ }, "packages/client-web": { "name": "@clickhouse/client-web", - "version": "1.18.4", + "version": "1.18.5", "license": "Apache-2.0", "dependencies": { - "@clickhouse/client-common": "1.18.4" + "@clickhouse/client-common": "1.18.5" } }, "vitest-poc": { diff --git a/package.json b/package.json index 2bc9057c3..230624f61 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "private": true, "engines": { - "node": ">=20" + "node": ">=20.19.0" }, "scripts": { "typecheck": "npm --workspaces run typecheck", @@ -46,7 +46,7 @@ "prepare": "husky" }, "devDependencies": { - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@faker-js/faker": "^10.3.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.71.0", @@ -63,7 +63,7 @@ "@vitest/coverage-istanbul": "^4.1.0", "@vitest/coverage-v8": "^4.1.0", "apache-arrow": "^21.0.0", - "eslint": "^9.39.4", + "eslint": "^10.2.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-expect-type": "^0.6.2", "eslint-plugin-prettier": "^5.5.5", diff --git a/packages/client-common/package.json b/packages/client-common/package.json index 3db9400c7..544727f3d 100644 --- a/packages/client-common/package.json +++ b/packages/client-common/package.json @@ -2,7 +2,7 @@ "name": "@clickhouse/client-common", "description": "Official JS client for ClickHouse DB - common types", "homepage": "https://clickhouse.com", - "version": "1.18.4", + "version": "1.18.5", "license": "Apache-2.0", "keywords": [ "clickhouse", diff --git a/packages/client-common/src/version.ts b/packages/client-common/src/version.ts index 71fe3020b..67472f30f 100644 --- a/packages/client-common/src/version.ts +++ b/packages/client-common/src/version.ts @@ -1 +1 @@ -export default '1.18.4' +export default '1.18.5' diff --git a/packages/client-node/__tests__/integration/node_response_headers_cap_client.test.ts b/packages/client-node/__tests__/integration/node_response_headers_cap_client.test.ts new file mode 100644 index 000000000..8dcfe964c --- /dev/null +++ b/packages/client-node/__tests__/integration/node_response_headers_cap_client.test.ts @@ -0,0 +1,164 @@ +import net, { type AddressInfo } from 'net' +import { afterEach, describe, it } from 'vitest' +import { createClient } from '../../src' +import type { ClickHouseClient } from '@clickhouse/client-common' + +// Verifies that the Node.js client honors the `max_response_headers_size` +// configuration option, which is forwarded to `http(s).request` as the +// `maxHeaderSize` option. +// +// Mirrors the scenarios from `node_response_headers_cap.test.ts`, but instead +// of using the raw Node `http` module the request is issued through +// `createClient` + `client.ping()`. A raw TCP server is still used to emit a +// hand-crafted HTTP/1.1 response with a large block of headers, bypassing the +// real ClickHouse server (and its own header-size limits). +describe('[Node.js] client max_response_headers_size behavior', () => { + let client: ClickHouseClient | undefined + + afterEach(async () => { + if (client !== undefined) { + await client.close() + client = undefined + } + }) + + // Build enough X-H-NNNN headers to roughly reach `targetBytes`. + function makeHeaders( + targetBytes: number, + ): Array<{ name: string; value: string }> { + const headers: Array<{ name: string; value: string }> = [] + let total = 0 + let i = 0 + while (total < targetBytes) { + const name = `X-H-${String(i).padStart(4, '0')}` + const value = 'a'.repeat(90) + headers.push({ name, value }) + total += name.length + 2 /* ": " */ + value.length + 2 /* CRLF */ + i++ + } + return headers + } + + // Raw TCP server that replies with a fixed HTTP/1.1 response containing + // the supplied headers. Bypasses Node's own server header limit entirely. + async function startServer( + headers: Array<{ name: string; value: string }>, + ): Promise<[net.Server, number]> { + const server = net.createServer((socket) => { + socket.once('data', () => { + const body = 'Ok.\n' + const headerBlob = headers + .map((h) => `${h.name}: ${h.value}\r\n`) + .join('') + const response = + 'HTTP/1.1 200 OK\r\n' + + `Content-Length: ${body.length}\r\n` + + 'Connection: close\r\n' + + headerBlob + + '\r\n' + + body + socket.write(response) + socket.end() + }) + }) + await new Promise((resolve) => { + server.listen(0, '127.0.0.1', () => resolve()) + }) + return [server, (server.address() as AddressInfo).port] + } + + type ClientResult = + | { ok: true } + | { ok: false; code?: string; message: string } + + async function tryClient( + port: number, + maxHeaderSize?: number, + ): Promise { + client = createClient({ + url: `http://127.0.0.1:${port}`, + // Force `Connection: close` so the client does not attempt to reuse + // sockets across the single response from our raw TCP server. + keep_alive: { enabled: false }, + max_response_headers_size: maxHeaderSize, + }) + const result = await client.ping() + if (result.success) { + return { ok: true } + } + const err = result.error as NodeJS.ErrnoException + return { ok: false, code: err.code, message: err.message } + } + + async function runScenario(params: { + payloadKB: number + maxHeaderSize?: number + }): Promise<{ result: ClientResult }> { + const headers = makeHeaders(params.payloadKB * 1024) + const [server, port] = await startServer(headers) + try { + const result = await tryClient(port, params.maxHeaderSize) + return { result } + } finally { + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) reject(err) + else resolve() + }) + }) + } + } + + // ── 16K bucket ──────────────────────────────────────────────── + it('A. ~15K payload at default 16K limit fits', async ({ expect }) => { + const { result } = await runScenario({ payloadKB: 15 }) + expect(result.ok).toBe(true) + }) + + it('B. ~20K payload at default 16K limit overflows (HPE_HEADER_OVERFLOW)', async ({ + expect, + }) => { + const { result } = await runScenario({ payloadKB: 20 }) + expect(result.ok).toBe(false) + if (result.ok) return + expect(result.code).toBe('HPE_HEADER_OVERFLOW') + }) + + // ── 32K bucket ──────────────────────────────────────────────── + it('C. ~20K payload raised to 32K limit fits', async ({ expect }) => { + const { result } = await runScenario({ + payloadKB: 20, + maxHeaderSize: 32 * 1024, + }) + expect(result.ok).toBe(true) + }) + + it('D. ~40K payload at 32K limit overflows (HPE_HEADER_OVERFLOW)', async ({ + expect, + }) => { + const { result } = await runScenario({ + payloadKB: 40, + maxHeaderSize: 32 * 1024, + }) + expect(result.ok).toBe(false) + if (result.ok) return + expect(result.code).toBe('HPE_HEADER_OVERFLOW') + }) + + // ── 64K bucket ──────────────────────────────────────────────── + it('E. ~40K payload raised to 64K limit fits', async ({ expect }) => { + const { result } = await runScenario({ + payloadKB: 40, + maxHeaderSize: 64 * 1024, + }) + expect(result.ok).toBe(true) + }) + + it('F. ~60K payload at 64K limit fits', async ({ expect }) => { + const { result } = await runScenario({ + payloadKB: 60, + maxHeaderSize: 64 * 1024, + }) + expect(result.ok).toBe(true) + }) +}) diff --git a/packages/client-node/__tests__/integration/node_stream_row_binary.test.ts b/packages/client-node/__tests__/integration/node_stream_row_binary.test.ts new file mode 100644 index 000000000..b59663d0d --- /dev/null +++ b/packages/client-node/__tests__/integration/node_stream_row_binary.test.ts @@ -0,0 +1,106 @@ +import { + ClickHouseLogLevel, + DefaultLogger, + LogWriter, + type ClickHouseClient, +} from '@clickhouse/client-common' +import { describe, it, beforeEach, afterEach, expect } from 'vitest' +import { createSimpleTable } from '@test/fixtures/simple_table' +import { createTestClient } from '@test/utils/client' +import { guid } from '@test/utils/guid' +import Stream from 'stream' +import { drainStreamInternal } from '../../src/connection/stream' + +describe('[Node.js] stream RowBinary insert', () => { + let client: ClickHouseClient + let tableName: string + let log_writer: LogWriter + + beforeEach(async () => { + client = createTestClient() + log_writer = new LogWriter( + new DefaultLogger(), + 'Connection', + ClickHouseLogLevel.OFF, + ) + tableName = `test_node_row_binary_stream_${guid()}` + await createSimpleTable(client, tableName) + }) + afterEach(async () => { + await client.close() + }) + + it('should send a RowBinary payload via a stream passed into the request body', async () => { + // Schema: id UInt64, name String, sku Array(UInt8) + // RowBinary encoding: + // UInt64 -> 8 bytes little-endian + // String -> varint length prefix + UTF-8 bytes + // Array(T) -> varint length prefix + items + const row1 = Buffer.concat([ + uint64LE(42n), + varString('foo'), + varUInt8Array([1, 2]), + ]) + const row2 = Buffer.concat([ + uint64LE(43n), + varString('bar'), + varUInt8Array([3, 4]), + ]) + + // Provide the payload via a Readable stream split across multiple chunks + // to exercise the streaming code path on the request body. + const stream = Stream.Readable.from([row1, row2], { objectMode: false }) + + const execResult = await client.exec({ + query: `INSERT INTO ${tableName} FORMAT RowBinary`, + values: stream, + }) + // The result stream contains nothing useful for an insert and should be + // immediately drained to release the socket. + await drainStreamInternal( + { + op: 'Insert', + query_id: execResult.query_id, + log_writer, + log_level: ClickHouseLogLevel.OFF, + }, + execResult.stream, + ) + + const rs = await client.query({ + query: `SELECT * FROM ${tableName} ORDER BY id ASC`, + format: 'JSONEachRow', + }) + expect(await rs.json()).toEqual([ + { id: '42', name: 'foo', sku: [1, 2] }, + { id: '43', name: 'bar', sku: [3, 4] }, + ]) + }) +}) + +function uint64LE(value: bigint): Buffer { + const buf = Buffer.alloc(8) + buf.writeBigUInt64LE(value) + return buf +} + +// LEB128 unsigned varint, used by ClickHouse for length prefixes in RowBinary. +function varUInt(value: number): Buffer { + const bytes: number[] = [] + let v = value + while (v >= 0x80) { + bytes.push((v & 0x7f) | 0x80) + v >>>= 7 + } + bytes.push(v & 0x7f) + return Buffer.from(bytes) +} + +function varString(value: string): Buffer { + const data = Buffer.from(value, 'utf8') + return Buffer.concat([varUInt(data.length), data]) +} + +function varUInt8Array(values: number[]): Buffer { + return Buffer.concat([varUInt(values.length), Buffer.from(values)]) +} diff --git a/packages/client-node/__tests__/integration/node_stream_row_binary_select.test.ts b/packages/client-node/__tests__/integration/node_stream_row_binary_select.test.ts new file mode 100644 index 000000000..6152f4968 --- /dev/null +++ b/packages/client-node/__tests__/integration/node_stream_row_binary_select.test.ts @@ -0,0 +1,102 @@ +import type { ClickHouseClient } from '@clickhouse/client-common' +import { describe, it, beforeEach, afterEach, expect } from 'vitest' +import { createSimpleTable } from '@test/fixtures/simple_table' +import { createTestClient } from '@test/utils/client' +import { guid } from '@test/utils/guid' +import type Stream from 'stream' + +describe('[Node.js] stream RowBinary select', () => { + let client: ClickHouseClient + let tableName: string + + beforeEach(async () => { + client = createTestClient() + tableName = `test_node_row_binary_select_stream_${guid()}` + await createSimpleTable(client, tableName) + await client.insert({ + table: tableName, + values: [ + { id: '42', name: 'foo', sku: [1, 2] }, + { id: '43', name: 'bar', sku: [3, 4] }, + ], + format: 'JSONEachRow', + }) + }) + afterEach(async () => { + await client.close() + }) + + it('should read a RowBinary payload from the response stream', async () => { + // Schema: id UInt64, name String, sku Array(UInt8) + const { stream } = await client.exec({ + query: `SELECT * FROM ${tableName} ORDER BY id ASC FORMAT RowBinary`, + }) + + const chunks: Buffer[] = [] + for await (const chunk of stream) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)) + } + const payload = Buffer.concat(chunks) + + // RowBinary decoding: + // UInt64 -> 8 bytes little-endian + // String -> varint length prefix + UTF-8 bytes + // Array(T) -> varint length prefix + items + const reader = new BufferReader(payload) + const rows: Array<{ id: string; name: string; sku: number[] }> = [] + while (!reader.eof()) { + const id = reader.readUInt64LE().toString() + const name = reader.readString() + const sku = reader.readUInt8Array() + rows.push({ id, name, sku }) + } + expect(reader.eof()).toBe(true) + expect(rows).toEqual([ + { id: '42', name: 'foo', sku: [1, 2] }, + { id: '43', name: 'bar', sku: [3, 4] }, + ]) + }) +}) + +class BufferReader { + private offset = 0 + constructor(private readonly buf: Buffer) {} + + eof(): boolean { + return this.offset >= this.buf.length + } + + readUInt64LE(): bigint { + const value = this.buf.readBigUInt64LE(this.offset) + this.offset += 8 + return value + } + + // LEB128 unsigned varint, used by ClickHouse for length prefixes in RowBinary. + readVarUInt(): number { + let value = 0 + let shift = 0 + while (true) { + const byte = this.buf[this.offset++] + value |= (byte & 0x7f) << shift + if ((byte & 0x80) === 0) { + return value >>> 0 + } + shift += 7 + } + } + + readString(): string { + const length = this.readVarUInt() + const value = this.buf.toString('utf8', this.offset, this.offset + length) + this.offset += length + return value + } + + readUInt8Array(): number[] { + const length = this.readVarUInt() + const slice = this.buf.subarray(this.offset, this.offset + length) + this.offset += length + return Array.from(slice) + } +} diff --git a/packages/client-node/__tests__/unit/node_config.test.ts b/packages/client-node/__tests__/unit/node_config.test.ts index da84b9815..d4c23e56a 100644 --- a/packages/client-node/__tests__/unit/node_config.test.ts +++ b/packages/client-node/__tests__/unit/node_config.test.ts @@ -282,5 +282,28 @@ describe('[Node.js] Config implementation details', () => { expect(createConnectionStub).toHaveBeenCalledTimes(1) expect(res).toEqual(fakeConnection) }) + + it('should forward max_response_headers_size to the connection factory', async () => { + const nodeConfig: NodeClickHouseClientConfigOptions = { + url: new URL('http://localhost:8123'), + max_response_headers_size: 64 * 1024, + } + const res = NodeConfigImpl.make_connection(nodeConfig as any, params) + expect(createConnectionStub).toHaveBeenCalledWith({ + connection_params: params, + tls: undefined, + keep_alive: { + enabled: true, + idle_socket_ttl: 2500, + }, + http_agent: undefined, + set_basic_auth_header: true, + capture_enhanced_stack_trace: false, + eagerly_destroy_stale_sockets: false, + max_response_headers_size: 64 * 1024, + } satisfies CreateConnectionParams) + expect(createConnectionStub).toHaveBeenCalledTimes(1) + expect(res).toEqual(fakeConnection) + }) }) }) diff --git a/packages/client-node/__tests__/unit/node_custom_agent_connection.test.ts b/packages/client-node/__tests__/unit/node_custom_agent_connection.test.ts index c5bf6775e..62c71a783 100644 --- a/packages/client-node/__tests__/unit/node_custom_agent_connection.test.ts +++ b/packages/client-node/__tests__/unit/node_custom_agent_connection.test.ts @@ -273,5 +273,84 @@ describe('[Node.js] NodeCustomAgentConnection', () => { httpRequestSpy.mockRestore() }) + + it('should forward max_response_headers_size as maxHeaderSize when set', () => { + const httpAgent = new Http.Agent() + const mockRequest = {} as Http.ClientRequest + const httpRequestSpy = vi + .spyOn(Http, 'request') + .mockReturnValue(mockRequest) + + const connection = new TestableCustomAgentConnection( + buildCustomAgentConnectionParams({ + url: new URL('http://localhost:8123'), + http_agent: httpAgent, + max_response_headers_size: 64 * 1024, + }), + ) + + const url = new URL('http://localhost:8123/?query_id=test') + const abortController = new AbortController() + connection.testCreateClientRequest({ + method: 'GET', + url, + headers: {}, + abort_signal: abortController.signal, + query: 'SELECT 1', + query_id: 'test-query-id', + log_writer: new LogWriter( + new TestLogger(), + 'CustomAgentConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + }) + + expect(httpRequestSpy).toHaveBeenCalledTimes(1) + const calledOptions = httpRequestSpy.mock + .calls[0][1] as Http.RequestOptions + expect(calledOptions.maxHeaderSize).toBe(64 * 1024) + + httpRequestSpy.mockRestore() + }) + + it('should not include maxHeaderSize when max_response_headers_size is undefined', () => { + const httpAgent = new Http.Agent() + const mockRequest = {} as Http.ClientRequest + const httpRequestSpy = vi + .spyOn(Http, 'request') + .mockReturnValue(mockRequest) + + const connection = new TestableCustomAgentConnection( + buildCustomAgentConnectionParams({ + url: new URL('http://localhost:8123'), + http_agent: httpAgent, + }), + ) + + const url = new URL('http://localhost:8123/?query_id=test') + const abortController = new AbortController() + connection.testCreateClientRequest({ + method: 'GET', + url, + headers: {}, + abort_signal: abortController.signal, + query: 'SELECT 1', + query_id: 'test-query-id', + log_writer: new LogWriter( + new TestLogger(), + 'CustomAgentConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + }) + + expect(httpRequestSpy).toHaveBeenCalledTimes(1) + const calledOptions = httpRequestSpy.mock + .calls[0][1] as Http.RequestOptions + expect(calledOptions).not.toHaveProperty('maxHeaderSize') + + httpRequestSpy.mockRestore() + }) }) }) diff --git a/packages/client-node/__tests__/unit/node_http_connection.test.ts b/packages/client-node/__tests__/unit/node_http_connection.test.ts new file mode 100644 index 000000000..f0f1e4e64 --- /dev/null +++ b/packages/client-node/__tests__/unit/node_http_connection.test.ts @@ -0,0 +1,120 @@ +import { describe, it, expect, vi } from 'vitest' +import Http from 'http' +import { ClickHouseLogLevel, LogWriter } from '@clickhouse/client-common' +import { TestLogger } from '../../../client-common/__tests__/utils/test_logger' +import type { NodeConnectionParams } from '../../src/connection' +import { NodeHttpConnection } from '../../src/connection' + +/** Extends NodeHttpConnection to expose protected methods for testing. */ +class TestableHttpConnection extends NodeHttpConnection { + public testCreateClientRequest( + ...args: Parameters + ): Http.ClientRequest { + return this.createClientRequest(...args) + } +} + +function buildHttpConnectionParams( + overrides?: Partial, +): NodeConnectionParams { + return { + url: new URL('http://localhost:8123'), + request_timeout: 30_000, + compression: { + decompress_response: false, + compress_request: false, + }, + max_open_connections: 10, + auth: { username: 'default', password: '', type: 'Credentials' }, + database: 'default', + clickhouse_settings: {}, + log_writer: new LogWriter( + new TestLogger(), + 'HttpConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + keep_alive: { + enabled: true, + idle_socket_ttl: 2500, + }, + set_basic_auth_header: true, + capture_enhanced_stack_trace: false, + eagerly_destroy_stale_sockets: false, + ...overrides, + } +} + +describe('[Node.js] NodeHttpConnection', () => { + describe('createClientRequest', () => { + it('should forward max_response_headers_size as maxHeaderSize when set', () => { + const mockRequest = {} as Http.ClientRequest + const httpRequestSpy = vi + .spyOn(Http, 'request') + .mockReturnValue(mockRequest) + + const connection = new TestableHttpConnection( + buildHttpConnectionParams({ + max_response_headers_size: 64 * 1024, + }), + ) + + const url = new URL('http://localhost:8123/?query_id=test') + const abortController = new AbortController() + connection.testCreateClientRequest({ + method: 'GET', + url, + headers: {}, + abort_signal: abortController.signal, + query: 'SELECT 1', + query_id: 'test-query-id', + log_writer: new LogWriter( + new TestLogger(), + 'HttpConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + }) + + expect(httpRequestSpy).toHaveBeenCalledTimes(1) + const calledOptions = httpRequestSpy.mock + .calls[0][1] as Http.RequestOptions + expect(calledOptions.maxHeaderSize).toBe(64 * 1024) + + httpRequestSpy.mockRestore() + }) + + it('should not include maxHeaderSize when max_response_headers_size is undefined', () => { + const mockRequest = {} as Http.ClientRequest + const httpRequestSpy = vi + .spyOn(Http, 'request') + .mockReturnValue(mockRequest) + + const connection = new TestableHttpConnection(buildHttpConnectionParams()) + + const url = new URL('http://localhost:8123/?query_id=test') + const abortController = new AbortController() + connection.testCreateClientRequest({ + method: 'GET', + url, + headers: {}, + abort_signal: abortController.signal, + query: 'SELECT 1', + query_id: 'test-query-id', + log_writer: new LogWriter( + new TestLogger(), + 'HttpConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + }) + + expect(httpRequestSpy).toHaveBeenCalledTimes(1) + const calledOptions = httpRequestSpy.mock + .calls[0][1] as Http.RequestOptions + expect(calledOptions).not.toHaveProperty('maxHeaderSize') + + httpRequestSpy.mockRestore() + }) + }) +}) diff --git a/packages/client-node/__tests__/unit/node_https_connection.test.ts b/packages/client-node/__tests__/unit/node_https_connection.test.ts index 314b69f92..ae71aac68 100644 --- a/packages/client-node/__tests__/unit/node_https_connection.test.ts +++ b/packages/client-node/__tests__/unit/node_https_connection.test.ts @@ -13,6 +13,11 @@ class TestableHttpsConnection extends NodeHttpsConnection { ): Http.OutgoingHttpHeaders { return this.buildRequestHeaders(params) } + public testCreateClientRequest( + ...args: Parameters + ): Http.ClientRequest { + return this.createClientRequest(...args) + } } function buildHttpsConnectionParams( @@ -167,4 +172,78 @@ describe('[Node.js] NodeHttpsConnection', () => { agentSpy.mockRestore() }) }) + + describe('createClientRequest', () => { + it('should forward max_response_headers_size as maxHeaderSize when set', () => { + const mockRequest = {} as Http.ClientRequest + const httpsRequestSpy = vi + .spyOn(Https, 'request') + .mockReturnValue(mockRequest) + + const connection = new TestableHttpsConnection( + buildHttpsConnectionParams({ + max_response_headers_size: 64 * 1024, + }), + ) + + const url = new URL('https://localhost:8443/?query_id=test') + const abortController = new AbortController() + connection.testCreateClientRequest({ + method: 'GET', + url, + headers: {}, + abort_signal: abortController.signal, + query: 'SELECT 1', + query_id: 'test-query-id', + log_writer: new LogWriter( + new TestLogger(), + 'HttpsConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + }) + + expect(httpsRequestSpy).toHaveBeenCalledTimes(1) + const calledOptions = httpsRequestSpy.mock + .calls[0][1] as Http.RequestOptions + expect(calledOptions.maxHeaderSize).toBe(64 * 1024) + + httpsRequestSpy.mockRestore() + }) + + it('should not include maxHeaderSize when max_response_headers_size is undefined', () => { + const mockRequest = {} as Http.ClientRequest + const httpsRequestSpy = vi + .spyOn(Https, 'request') + .mockReturnValue(mockRequest) + + const connection = new TestableHttpsConnection( + buildHttpsConnectionParams(), + ) + + const url = new URL('https://localhost:8443/?query_id=test') + const abortController = new AbortController() + connection.testCreateClientRequest({ + method: 'GET', + url, + headers: {}, + abort_signal: abortController.signal, + query: 'SELECT 1', + query_id: 'test-query-id', + log_writer: new LogWriter( + new TestLogger(), + 'HttpsConnectionTest', + ClickHouseLogLevel.OFF, + ), + log_level: ClickHouseLogLevel.OFF, + }) + + expect(httpsRequestSpy).toHaveBeenCalledTimes(1) + const calledOptions = httpsRequestSpy.mock + .calls[0][1] as Http.RequestOptions + expect(calledOptions).not.toHaveProperty('maxHeaderSize') + + httpsRequestSpy.mockRestore() + }) + }) }) diff --git a/packages/client-node/package.json b/packages/client-node/package.json index 27226ab9f..1cdb78988 100644 --- a/packages/client-node/package.json +++ b/packages/client-node/package.json @@ -2,7 +2,7 @@ "name": "@clickhouse/client", "description": "Official JS client for ClickHouse DB - Node.js implementation", "homepage": "https://clickhouse.com", - "version": "1.18.4", + "version": "1.18.5", "license": "Apache-2.0", "keywords": [ "clickhouse", @@ -25,6 +25,10 @@ ], "agents": { "skills": [ + { + "name": "clickhouse-js-node-coding", + "path": "./skills/clickhouse-js-node-coding" + }, { "name": "clickhouse-js-node-troubleshooting", "path": "./skills/clickhouse-js-node-troubleshooting" @@ -40,7 +44,7 @@ "build": "rm -rf dist; tsc" }, "dependencies": { - "@clickhouse/client-common": "1.18.4" + "@clickhouse/client-common": "1.18.5" }, "devDependencies": { "simdjson": "^0.9.2" diff --git a/packages/client-node/src/config.ts b/packages/client-node/src/config.ts index 5369e4d56..19e223504 100644 --- a/packages/client-node/src/config.ts +++ b/packages/client-node/src/config.ts @@ -59,6 +59,23 @@ export type NodeClickHouseClientConfigOptions = * please provide your feedback in the repository. * @default false (disabled) */ capture_enhanced_stack_trace?: boolean + /** Override the maximum length (in bytes) of HTTP response headers accepted from the server. + * Forwarded as the `maxHeaderSize` option to {@link http.request} / {@link https.request}. + * + * This is primarily useful for long-running queries that rely on + * `send_progress_in_http_headers`: ClickHouse keeps appending an `X-ClickHouse-Progress` + * header on every progress interval, and once the cumulative size exceeds the Node.js + * default (~16 KB), the request fails with `HPE_HEADER_OVERFLOW`. Setting a higher value + * here (e.g. `64 * 1024` or `1024 * 1024`) lifts that limit per client without requiring + * the global `--max-http-header-size` Node.js CLI flag or `NODE_OPTIONS` environment variable. + * + * When `undefined`, the Node.js default (or the value of `--max-http-header-size`) applies. + * + * Has no effect when a custom {@link http_agent} is provided that uses a different + * request implementation; for the bundled HTTP/HTTPS connections it is passed straight + * through to the request options. + * @default undefined */ + max_response_headers_size?: number } interface BasicTLSOptions { @@ -138,6 +155,7 @@ export const NodeConfigImpl: Required< http_agent: nodeConfig.http_agent, keep_alive, tls, + max_response_headers_size: nodeConfig.max_response_headers_size, }) }, values_encoder: (jsonHandling: JSONHandling) => diff --git a/packages/client-node/src/connection/create_connection.ts b/packages/client-node/src/connection/create_connection.ts index 5527b7232..2f6760742 100644 --- a/packages/client-node/src/connection/create_connection.ts +++ b/packages/client-node/src/connection/create_connection.ts @@ -17,6 +17,7 @@ export interface CreateConnectionParams { set_basic_auth_header: boolean capture_enhanced_stack_trace: boolean eagerly_destroy_stale_sockets?: boolean + max_response_headers_size?: number } /** A factory for easier mocking after Node.js 22.18 */ @@ -30,6 +31,7 @@ export class NodeConnectionFactory { set_basic_auth_header, capture_enhanced_stack_trace, eagerly_destroy_stale_sockets = false, + max_response_headers_size, }: CreateConnectionParams): NodeBaseConnection { if (http_agent !== undefined) { return new NodeCustomAgentConnection({ @@ -39,6 +41,7 @@ export class NodeConnectionFactory { keep_alive, // only used to enforce proper KeepAlive headers http_agent, eagerly_destroy_stale_sockets, + max_response_headers_size, }) } switch (connection_params.url.protocol) { @@ -49,6 +52,7 @@ export class NodeConnectionFactory { capture_enhanced_stack_trace, keep_alive, eagerly_destroy_stale_sockets, + max_response_headers_size, }) case 'https:': return new NodeHttpsConnection({ @@ -58,6 +62,7 @@ export class NodeConnectionFactory { keep_alive, tls, eagerly_destroy_stale_sockets, + max_response_headers_size, }) default: throw new Error('Only HTTP and HTTPS protocols are supported') diff --git a/packages/client-node/src/connection/node_base_connection.ts b/packages/client-node/src/connection/node_base_connection.ts index 3d271a288..fd0a143eb 100644 --- a/packages/client-node/src/connection/node_base_connection.ts +++ b/packages/client-node/src/connection/node_base_connection.ts @@ -44,6 +44,15 @@ export type NodeConnectionParams = ConnectionParams & { * Eagerly destroy the sockets that are considered stale (idle for more than `idle_socket_ttl`), without waiting for the timeout to trigger. This allows to free up the stale sockets in case of longer event loop delays. */ eagerly_destroy_stale_sockets: boolean + /** + * Optional override for {@link Http.RequestOptions.maxHeaderSize} forwarded to + * `http(s).request`. Useful for long-running queries that accumulate many + * `X-ClickHouse-Progress` headers and would otherwise hit the Node.js default + * (~16 KB) total response header limit. + * + * When `undefined`, the Node.js default applies. + */ + max_response_headers_size?: number } export type TLSParams = diff --git a/packages/client-node/src/connection/node_custom_agent_connection.ts b/packages/client-node/src/connection/node_custom_agent_connection.ts index 6d4eebf95..b6a898e94 100644 --- a/packages/client-node/src/connection/node_custom_agent_connection.ts +++ b/packages/client-node/src/connection/node_custom_agent_connection.ts @@ -35,6 +35,9 @@ export class NodeCustomAgentConnection extends NodeBaseConnection { timeout: this.params.request_timeout, signal: params.abort_signal, headers, + ...(this.params.max_response_headers_size !== undefined && { + maxHeaderSize: this.params.max_response_headers_size, + }), }) } } diff --git a/packages/client-node/src/connection/node_http_connection.ts b/packages/client-node/src/connection/node_http_connection.ts index 3e4c2efb7..eb25493cf 100644 --- a/packages/client-node/src/connection/node_http_connection.ts +++ b/packages/client-node/src/connection/node_http_connection.ts @@ -25,6 +25,9 @@ export class NodeHttpConnection extends NodeBaseConnection { timeout: this.params.request_timeout, signal: params.abort_signal, headers, + ...(this.params.max_response_headers_size !== undefined && { + maxHeaderSize: this.params.max_response_headers_size, + }), }) } } diff --git a/packages/client-node/src/connection/node_https_connection.ts b/packages/client-node/src/connection/node_https_connection.ts index 5ea79bfb4..2a0d693a7 100644 --- a/packages/client-node/src/connection/node_https_connection.ts +++ b/packages/client-node/src/connection/node_https_connection.ts @@ -72,6 +72,9 @@ export class NodeHttpsConnection extends NodeBaseConnection { timeout: this.params.request_timeout, signal: params.abort_signal, headers, + ...(this.params.max_response_headers_size !== undefined && { + maxHeaderSize: this.params.max_response_headers_size, + }), }) } } diff --git a/packages/client-node/src/utils/stream.ts b/packages/client-node/src/utils/stream.ts index 0af193d93..1be3151fd 100644 --- a/packages/client-node/src/utils/stream.ts +++ b/packages/client-node/src/utils/stream.ts @@ -34,6 +34,7 @@ export async function getAsText(stream: Stream.Readable): Promise { ) { throw new Error( `The response length exceeds the maximum allowed size of V8 String: ${MAX_STRING_LENGTH} characters.`, + { cause: err }, ) } throw err diff --git a/packages/client-node/src/version.ts b/packages/client-node/src/version.ts index 71fe3020b..67472f30f 100644 --- a/packages/client-node/src/version.ts +++ b/packages/client-node/src/version.ts @@ -1 +1 @@ -export default '1.18.4' +export default '1.18.5' diff --git a/packages/client-web/package.json b/packages/client-web/package.json index d4bf6ff90..5b10a8819 100644 --- a/packages/client-web/package.json +++ b/packages/client-web/package.json @@ -2,7 +2,7 @@ "name": "@clickhouse/client-web", "description": "Official JS client for ClickHouse DB - Web API implementation", "homepage": "https://clickhouse.com", - "version": "1.18.4", + "version": "1.18.5", "license": "Apache-2.0", "keywords": [ "clickhouse", @@ -31,6 +31,6 @@ "build": "rm -rf dist; tsc" }, "dependencies": { - "@clickhouse/client-common": "1.18.4" + "@clickhouse/client-common": "1.18.5" } } diff --git a/packages/client-web/src/version.ts b/packages/client-web/src/version.ts index 71fe3020b..67472f30f 100644 --- a/packages/client-web/src/version.ts +++ b/packages/client-web/src/version.ts @@ -1 +1 @@ -export default '1.18.4' +export default '1.18.5' diff --git a/skills/clickhouse-js-node-coding/SKILL.md b/skills/clickhouse-js-node-coding/SKILL.md new file mode 100644 index 000000000..265039768 --- /dev/null +++ b/skills/clickhouse-js-node-coding/SKILL.md @@ -0,0 +1,146 @@ +--- +name: clickhouse-js-node-coding +description: > + Write idiomatic application code with the ClickHouse Node.js client + (`@clickhouse/client`). Use this skill whenever a user is *building* against + the Node.js client — configuring the client, pinging, inserting rows in JSON + or raw formats, selecting and parsing results, binding query parameters, + managing sessions and temporary tables, working with data types like + `Date`/`DateTime`/`Decimal`/`Time`/`Time64`/`Dynamic`/`Variant`/`JSON`, or + customizing JSON parsing. Trigger on phrases like "how do I insert…", "how + do I select…", "what format should I use…", "how do I parameterize…", "how + do I configure the client…". Do NOT use for browser/Web client code, for + performance/streaming/Parquet questions (see `examples/node/performance/`), + or for diagnosing errors and unexpected behavior (see + clickhouse-js-node-troubleshooting). +--- + +# ClickHouse Node.js Client — Coding + +Reference: https://clickhouse.com/docs/integrations/javascript + +> **⚠️ Node.js runtime only.** This skill covers the `@clickhouse/client` +> package running in a **Node.js runtime** exclusively — including **Next.js +> Node runtime** API routes, React Server Components, Server Actions, and +> standard Node.js processes. Do **not** apply this skill to browser client +> components, Web Workers, **Next.js Edge runtime**, Cloudflare Workers, or +> any usage of `@clickhouse/client-web`. For browser/edge environments, the +> correct package is `@clickhouse/client-web`. + +--- + +## How to Use This Skill + +1. **Match the user's intent** to a row in the Task Index below and read the + corresponding reference file before writing code. After reading it, scan any + **Answer checklist** in that reference and make sure the final answer covers + each relevant item; those checklists capture details users usually need but + are easy to omit in short answers. +2. **Always import from `@clickhouse/client`** (never `@clickhouse/client-web`) + and create a single client with `createClient({ url })` or rely on + supported defaults when appropriate. Close it with `await client.close()` + during graceful shutdown. +3. **Prefer `JSONEachRow` for typical row inserts/selects** unless the user + has already chosen another format or is streaming raw bytes (CSV / TSV / + Parquet — see `examples/node/performance/`). + **Note on `clickhouse_settings`:** settings passed to `createClient` are + defaults for every request; they can be overridden per-call by passing + `clickhouse_settings` directly to `insert()`, `query()`, or `command()`. + Always mention this when the user configures settings at the client level. +4. **Always use `query_params` for user-supplied values** — never template- + literal-interpolate them into SQL. See `reference/query-parameters.md`. +5. **Pick the right method for the job:** + - `client.insert()` — write rows. + - `client.query()` + `resultSet.json()` / `.text()` / `.stream()` — read + rows that return data. + - `client.command()` — DDL and other statements that don't return rows + (`CREATE`, `DROP`, `TRUNCATE`, `ALTER`, `SET` in a session, etc.). + - `client.exec()` — when you need the raw response stream of an arbitrary + statement (rare in coding scenarios). + - `client.ping()` — health check; returns `{ success, error? }`, never + throws on connection failure. +6. **Note version constraints** when relevant. Examples: + - `pathname` config option: client `>= 1.0.0`. + - `BigInt` values in `query_params`: client `>= 1.15.0`. + - `TupleParam` and JS `Map` in `query_params`: client `>= 1.9.0`. + - Configurable `json.parse` / `json.stringify`: client `>= 1.14.0`. + - `Time` / `Time64` data types: ClickHouse server `>= 25.6`. + - `Dynamic` / `Variant` / new `JSON` types: ClickHouse server `>= 24.1` / + `24.5` / `24.8` (no longer experimental since `25.3`). +7. **Show a runnable snippet**, not pseudo-code. The examples in + [`examples/node/coding/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/coding) + are all self-contained and runnable against the repo's `docker-compose up` + setup — pattern your snippet after them. + +--- + +## Task Index + +Identify the user's task and read the matching reference file. + +| Task | Triggers / symptoms | Reference file | +| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| **Configure / connect the client** | Building a `createClient` call, URL parameters, `clickhouse_settings`, default format, custom HTTP headers | `reference/client-configuration.md` | +| **Ping the server** | Health checks, readiness probes, "is ClickHouse up?" | `reference/ping.md` | +| **Choose an insert format** | "Which format should I use to insert?", JSON vs raw, `JSONEachRow` vs `JSON` vs `JSONObjectEachRow` | `reference/insert-formats.md` | +| **Insert into a subset of columns / different database** | `insert({ columns })`, excluding columns, ephemeral columns, cross-DB inserts | `reference/insert-columns.md` | +| **Insert values, expressions, dates, decimals** | `INSERT … VALUES` with SQL functions, `Date`/`DateTime` from JS, `Decimal` precision, `INSERT … SELECT` | `reference/insert-values.md` | +| **Async inserts (server-side batching)** | `async_insert=1`, fire-and-forget vs wait-for-ack | `reference/async-insert.md` | +| **Select and parse results** | `JSONEachRow` reads, `JSON` with metadata, picking a select format | `reference/select-formats.md` | +| **Parameterize queries** | Binding values, special characters / escaping, "SQL injection?", `{name: Type}` syntax | `reference/query-parameters.md` | +| **Sessions & temporary tables** | `session_id`, `CREATE TEMPORARY TABLE`, per-session `SET` commands | `reference/sessions.md` | +| **Modern data types** | `Dynamic`, `Variant`, `JSON` (object), `Time`, `Time64` | `reference/data-types.md` | +| **Custom JSON parse/stringify** | Plug in `JSONBig` / `safe-stable-stringify` / a `BigInt`-aware serializer | `reference/custom-json.md` | + +--- + +## Conventions used in answers + +- Always show `import { createClient } from '@clickhouse/client'` (Node, never + Web). For things that require a runtime API, prefer `node:` built-ins + (e.g., `import * as crypto from 'node:crypto'`). +- Always `await client.close()` at the end of self-contained snippets; in + long-running services, close on graceful shutdown. +- Prefer top-level `await` in snippets to match the style of + `examples/node/coding/*.ts`. +- For inserts, prefer `format: 'JSONEachRow'` and `values: [...]` unless the + user's scenario requires otherwise. +- For selects, prefer `await (await client.query({...})).json()` for + small / medium result sets; for streaming, see `examples/node/performance/`. +- When showing parameter binding, use ClickHouse's native `{name: Type}` + syntax — never `$1`, `?`, or `:name`. +- For DDL inside a cluster or behind a load balancer, set + `clickhouse_settings: { wait_end_of_query: 1 }` on the `command()` call so + the server only acknowledges after the change is applied. See + https://clickhouse.com/docs/en/interfaces/http/#response-buffering. + +--- + +## Out of scope + +This skill covers day-to-day coding against `@clickhouse/client` (Node). +The following topics are intentionally **not** covered here: + +- **Errors, hangs, type mismatches, proxy pathname surprises, log silence, + socket hang-ups, `ECONNRESET`** → use the + `clickhouse-js-node-troubleshooting` skill. +- **Streaming, Parquet, file streams, server-side bulk moves, progress + streaming, async-insert throughput tuning** — see + [`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance). +- **TLS, RBAC / read-only users, deeper SQL-injection guidance** — see + [`examples/node/security/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/security). +- **`CREATE TABLE` patterns, deployment-shaped connection strings, + replication / sharding choices** — see + [`examples/node/schema-and-deployments/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/schema-and-deployments). +- **Browser, Web Worker, Next.js Edge, Cloudflare Workers** — use + `@clickhouse/client-web` and see + [`examples/web/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/web). + +--- + +## Still Stuck? + +- [`examples/node/coding/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/coding) — the runnable corpus this skill is built on. +- [ClickHouse JS client docs](https://clickhouse.com/docs/integrations/javascript) +- [ClickHouse supported formats](https://clickhouse.com/docs/interfaces/formats) +- [ClickHouse data types](https://clickhouse.com/docs/sql-reference/data-types) diff --git a/skills/clickhouse-js-node-coding/evals/evals.json b/skills/clickhouse-js-node-coding/evals/evals.json new file mode 100644 index 000000000..50cf85025 --- /dev/null +++ b/skills/clickhouse-js-node-coding/evals/evals.json @@ -0,0 +1,103 @@ +{ + "skill_name": "clickhouse-js-node-coding", + "evals": [ + { + "id": 0, + "prompt": "I'm setting up clickhouse client in a Node service. I want to point it at https://my.host:8124, use database 'analytics', user 'bob' / password 'secret', set application name 'my_app', and turn on async_insert without waiting for ack. What's the cleanest way to express that?", + "expected_output": "A createClient call (Node, not Web) that sets url, username/password (or embeds them in the URL), database, application, and clickhouse_settings: { async_insert: 1, wait_for_async_insert: 0 }. Optionally mentions the equivalent URL-parameter form (ch_async_insert=1&ch_wait_for_async_insert=0) and that URL params override the config object.", + "files": [], + "expectations": [ + "Uses createClient from @clickhouse/client (Node, not Web).", + "Either passes a single URL string with auth + ?ch_async_insert=1&ch_wait_for_async_insert=0, or a config object with database, username, password, application, clickhouse_settings.", + "Mentions or implies that URL parameters override the config object if both are provided.", + "Does not suggest using URL parameters in code and instead suggests that the URL should be read from environment variables or a config file.", + "Does not construct the URL with parameters directly in code neither using string concatenation nor query objects.", + "Suggests using `await client.close()` during graceful shutdown.", + "Suggests that settings in `clickhouse_settings` can be overridden per-query by passing them inside the individual `insert()` or `query()` call for finer control." + ] + }, + { + "id": 1, + "prompt": "How do I do a health check against ClickHouse from Node? I want to return 200/503 from an Express endpoint based on whether ClickHouse is reachable.", + "expected_output": "Use await client.ping(), branch on { success } (no try/catch needed) — return 200 on success and 503 on failure, optionally surfacing the error.", + "files": [], + "expectations": [ + "Uses await client.ping() and reads { success, error } directly — does NOT wrap it in try/catch as the only check.", + "Maps success === true to 200 and success === false to 503.", + "Suggests lowering request_timeout to make the probe fail fast.", + "Explains the difference between `client.ping()` (checks connectivity only and ignores credentials by default) and `client.ping({ select: true })` (lightweight query that also checks auth and query processing) and when to use each." + ] + }, + { + "id": 2, + "prompt": "I have an array of about 10k plain JS objects I want to insert into a MergeTree table. What's the right format and call?", + "expected_output": "client.insert with format: 'JSONEachRow' and values: . No streaming / Parquet needed for this size.", + "files": [], + "expectations": [ + "Uses client.insert({ table, values, format: 'JSONEachRow' }).", + "Notes that the array can be passed directly to values — no need to stringify or stream for a few thousand rows.", + "Does NOT recommend a streaming/Parquet flow for this size.", + "Mentions `JSONCompact*` formats as an alternative for bigger payloads" + ] + }, + { + "id": 3, + "prompt": "My table has columns (id, name, created_at, internal_hash) but the rows I have only contain id and name. How do I insert just those two columns?", + "expected_output": "Use the columns option on client.insert: either columns: ['id', 'name'] (allowlist) or columns: { except: ['created_at', 'internal_hash'] } (excludelist). Omitted columns get their declared defaults.", + "files": [], + "expectations": [ + "Uses client.insert with the columns: ['id', 'name'] option.", + "Mentions the alternative columns: { except: [...] } form.", + "Notes that omitted columns will receive their server-side defaults." + ] + }, + { + "id": 4, + "prompt": "I want to call: SELECT * FROM users WHERE country = '' AND signup_date > ''. How should I pass those values from JS?", + "expected_output": "Use parameterized queries with the ClickHouse {name: Type} syntax and query_params: { country: ..., signup_date: ... }. Explicitly warn against template-literal interpolation (SQL injection).", + "files": [], + "expectations": [ + "Uses {name: Type} parameter syntax (e.g., {country: String}, {signup_date: Date}) and query_params.", + "Explicitly warns against template-literal interpolation as a SQL injection risk.", + "Does NOT suggest $1/?/:name placeholders." + ] + }, + { + "id": 5, + "prompt": "I'm doing CREATE TEMPORARY TABLE and then SELECT from it in a follow-up call. They keep disappearing between calls. What am I missing?", + "expected_output": "Temporary tables are scoped to a session — set a stable session_id (e.g., crypto.randomUUID()) on the client (or per-call) so consecutive requests share server-side state. Also flag the load-balancer/Cloud caveat (replica-aware routing).", + "files": [], + "expectations": [ + "Explains that temporary tables are scoped to a session and require a stable session_id across calls.", + "Shows setting session_id either on createClient or via per-call session_id.", + "Mentions the load-balancer / ClickHouse Cloud caveat (sessions are pinned to a node; recommend replica-aware routing or a single-node connection).", + "Explicitly explains that parallel calls with the same session_id will result in an error as ClickHouse does not allow concurrent queries within the same session_id.", + "Explicitly advises against using session_id in client configuration for a global / module static client", + "When session_id is used as a client option it should suggest configuring the maximum number of connections to 1 to minimize concurrency issues at the client level." + ] + }, + { + "id": 6, + "prompt": "I'm running 25.x ClickHouse. I want to store a JSON object per row and read it back as a real JS object, not a JSON string. How do I do that with the Node client?", + "expected_output": "Use the new JSON column type (>= 24.8, no longer experimental since 25.3). CREATE TABLE with a JSON column, insert with format: 'JSONEachRow' passing JS objects, select with JSONEachRow — values come back as parsed JS objects with no manual JSON.parse.", + "files": [], + "expectations": [ + "Uses the JSON column type and format: 'JSONEachRow' for both insert and select.", + "Inserts a real JS object as the column value (no JSON.stringify) and shows it returns as a parsed object.", + "Mentions the relevant ClickHouse server version (>= 24.8 introduced; non-experimental since 25.3) and, if needed on older servers, allow_experimental_json_type." + ] + }, + { + "id": 7, + "prompt": "Our IDs are UInt64 and we don't want them coming back as strings or losing precision.", + "expected_output": "Yes — pass a custom { parse, stringify } via the json config option (>= 1.14.0). Show wiring up json-bigint (or similar) so 64-bit integers are parsed as BigInt. Mention output_format_json_quote_64bit_integers: 0 so the server emits unquoted ints. Note that switching to native Number would lose precision and is the wrong fix.", + "files": [], + "expectations": [ + "Shows passing custom { parse, stringify } via the json config option on createClient.", + "Notes the >= 1.14.0 client requirement for the json option.", + "Mentions output_format_json_quote_64bit_integers: 0 (default since 25.8) so 64-bit integers come back unquoted and parseable as BigInt.", + "Warns that switching to native Number would lose precision and is the wrong fix." + ] + } + ] +} diff --git a/skills/clickhouse-js-node-coding/reference/async-insert.md b/skills/clickhouse-js-node-coding/reference/async-insert.md new file mode 100644 index 000000000..09c32ffbf --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/async-insert.md @@ -0,0 +1,103 @@ +# Async Inserts + +> **Applies to:** all client versions; the relevant settings are server-side. +> See https://clickhouse.com/docs/en/optimize/asynchronous-inserts. + +Backing example: +[`examples/node/coding/async_insert.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/async_insert.ts). + +> **When to use async inserts:** when many small inserts arrive concurrently +> (e.g., one per HTTP request) and you don't want to maintain a client-side +> batching layer. ClickHouse will batch them server-side. This is also the +> recommended ingestion pattern for **ClickHouse Cloud**. + +> **When _not_ to use async inserts:** when you already build large batches +> client-side (e.g., from a stream). Plain inserts are simpler and lower +> latency. For raw throughput tuning of large async-insert workloads, see +> [`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance). + +## Setup + +Enable on the client (or per-request) via `clickhouse_settings`: + +```ts +import { createClient, ClickHouseError } from '@clickhouse/client' + +const client = createClient({ + url: process.env.CLICKHOUSE_URL, + password: process.env.CLICKHOUSE_PASSWORD, + max_open_connections: 10, + clickhouse_settings: { + async_insert: 1, + wait_for_async_insert: 1, // wait for ack from server + async_insert_max_data_size: '1000000', + async_insert_busy_timeout_ms: 1000, + }, +}) +``` + +## Concurrent small inserts + +Each call still uses the client's normal `insert()` API — the server merges +the batches. + +```ts +const promises = [...new Array(10)].map(async () => { + const values = [...new Array(1000).keys()].map(() => ({ + id: Math.floor(Math.random() * 100_000) + 1, + data: Math.random().toString(36).slice(2), + })) + + await client + .insert({ table: 'async_insert_example', values, format: 'JSONEachRow' }) + .catch((err) => { + if (err instanceof ClickHouseError) { + // err.code matches a row in system.errors + console.error(`ClickHouse error ${err.code}:`, err) + return + } + console.error('Insert failed:', err) + }) +}) + +await Promise.all(promises) +``` + +## `wait_for_async_insert` — fire-and-forget vs ack + +| `wait_for_async_insert` | Promise resolves when… | Trade-off | +| ----------------------- | ------------------------------------------------- | ------------------------------------------------------------------- | +| `1` (default) | Server has flushed the batch to the table | Slower per call; insert errors surface to the client | +| `0` | Server accepted the row into its in-memory buffer | Faster; flush errors won't surface — only validation/parsing errors | + +With `wait_for_async_insert: 1`, expect each insert call to take roughly +`async_insert_busy_timeout_ms` to resolve when traffic is light, because the +server waits for more rows or for the timer to fire before flushing. + +## Combining DDL with async inserts + +When creating tables in scripts that immediately insert, ack the DDL with +`wait_end_of_query: 1` so the table is ready before the first insert: + +```ts +await client.command({ + query: ` + CREATE OR REPLACE TABLE async_insert_example (id Int32, data String) + ENGINE MergeTree ORDER BY id + `, + clickhouse_settings: { wait_end_of_query: 1 }, +}) +``` + +## Common pitfalls + +- **Setting `async_insert` per call but expecting client-side batching.** + The client still issues each `insert()` as a separate HTTP request — the + batching happens on the server. +- **Confusing `wait_for_async_insert` (async-insert ack) with + `wait_end_of_query` (DDL ack).** They are unrelated. +- **Treating a resolved insert under `wait_for_async_insert: 0` as + durably written.** It only means the server accepted the bytes; flush + failures will not surface to the client. +- **Not handling `ClickHouseError`.** It exposes `err.code`, which maps to + rows in the `system.errors` table — use it to decide whether to retry. diff --git a/skills/clickhouse-js-node-coding/reference/client-configuration.md b/skills/clickhouse-js-node-coding/reference/client-configuration.md new file mode 100644 index 000000000..894fd2fc4 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/client-configuration.md @@ -0,0 +1,159 @@ +# Client Configuration + +> **Applies to:** all versions, with these notable additions: +> +> - `pathname` config option: client `>= 1.0.0`. +> - `clickhouse_setting_*` / `ch_*` URL parameters: client `>= 1.0.0`. +> - `keep_alive.idle_socket_ttl` (Node-only): client `>= 1.0.0`. + +Backing examples: +[`examples/node/coding/url_configuration.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/url_configuration.ts), +[`examples/node/coding/clickhouse_settings.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/clickhouse_settings.ts), +[`examples/node/coding/default_format_setting.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/default_format_setting.ts). + +## Answer checklist + +When answering configuration questions, include the relevant points: + +- Show `createClient` from `@clickhouse/client` with explicit fields when the + user is writing code; this is easier to read and review than encoding + everything into a URL string. +- When mentioning the URL form for environment variables / DSNs: show a **Bash** + `export` with the literal URL value, and `createClient({ url: process.env.CLICKHOUSE_URL })` + in the Node code. **Never construct a URL in application code** — no string + concatenation, no template literals, no query-string builders. +- If URL parameters and object fields both set the same option, URL parameters + override the rest of the configuration object. +- If `clickhouse_settings` appear on `createClient`, explain that they are + defaults for every request and can be overridden on individual `query()`, + `insert()`, `command()`, or `exec()` calls. +- Remind long-running services to close the client during graceful shutdown. +- The `application` field sets the name that appears in `system.query_log`. + Do **not** mention any specific HTTP header name — the client handles header + mapping internally and the header names are an implementation detail. + +## Minimal client + +```ts +import { createClient } from '@clickhouse/client' + +const client = createClient({ + url: process.env.CLICKHOUSE_URL, // defaults to 'http://localhost:8123' + username: process.env.CLICKHOUSE_USER, // defaults to 'default' + password: process.env.CLICKHOUSE_PASSWORD, // defaults to '' + database: 'analytics', // defaults to 'default' +}) +// ... your queries ... +await client.close() +``` + +`url` accepts a string or a `URL` object. The accepted string format is: + +``` +http[s]://[username:password@]hostname:port[/database][?param1=value1¶m2=value2] +``` + +## Configuration via URL parameters + +A fixed allowlist of config fields can be set as URL query parameters +(plus any key prefixed with `clickhouse_setting_` / `ch_` / `http_header_`). +**Supported URL parameters override the corresponding values in the rest of +the configuration object** — when they do, the client logs a warning. +Unknown URL parameters cause `createClient` to throw +`Unknown URL parameters: ...` +(see [`packages/client-common/src/config.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/packages/client-common/src/config.ts) for the shared allowlist, and [`packages/client-node/src/config.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/packages/client-node/src/config.ts) for Node-specific URL parameters). + +Supported non-prefixed keys parsed by `client-common`: `application`, +`session_id`, `pathname`, `access_token`, `request_timeout`, +`max_open_connections`, `compression_request`, `compression_response`, +`log_level`, `keep_alive_enabled`. Additionally, Node supports +`keep_alive_idle_socket_ttl` via the Node-specific config implementation. +Anything else must be passed via the config object on `createClient`. + +Prefer explicit object fields in application code. Use the URL form when the +application receives one connection string from an environment variable, secret +manager, or config file. The URL value belongs in the environment, not in the +source code — show it as a shell export and read it in Node: + +```bash +# In your shell environment / deployment config (e.g. .env, Kubernetes secret): +export CLICKHOUSE_URL='https://bob:secret@my.host:8124/analytics?application=my_analytics_app&ch_async_insert=1&ch_wait_for_async_insert=0' +``` + +```ts +// In your Node.js code — no URL construction needed: +const client = createClient({ url: process.env.CLICKHOUSE_URL }) +``` + +The same connection can also be expressed as an explicit config object (useful when you want to document each field individually): + +```ts +import { createClient } from '@clickhouse/client' + +createClient({ + url: 'https://my.host:8124', + username: 'bob', + password: 'secret', + database: 'analytics', + application: 'my_analytics_app', + clickhouse_settings: { async_insert: 1, wait_for_async_insert: 0 }, +}) +``` + +## Per-client vs per-request `clickhouse_settings` ⭐ + +> **Always mention this when discussing `clickhouse_settings`:** settings set +> on `createClient` are defaults; any individual call can override them. + +Settings on `createClient` apply to every request. Settings on a single +operation (`query`, `insert`, `command`, `exec`) override the client defaults +for **that call only**. + +```ts +const client = createClient({ + clickhouse_settings: { + date_time_input_format: 'best_effort', // applied to every request + }, +}) + +const rows = await client.query({ + query: 'SELECT number FROM system.numbers LIMIT 2', + format: 'JSONEachRow', + clickhouse_settings: { + output_format_json_quote_64bit_integers: 1, // overrides client default for this call + }, +}) +``` + +## `default_format` for `exec()` + +`client.exec()` runs an arbitrary statement and returns a stream. If your +query has no trailing `FORMAT …` clause, set `default_format` so the server +knows what to send back, then wrap the response in a `ResultSet`: + +```ts +import { createClient, ResultSet } from '@clickhouse/client' + +const client = createClient() +const format = 'JSONCompactEachRowWithNamesAndTypes' +const { stream, query_id } = await client.exec({ + query: 'SELECT database, name, engine FROM system.tables LIMIT 5', + clickhouse_settings: { default_format: format }, +}) +const rs = new ResultSet(stream, format, query_id) +console.log(await rs.json()) +await client.close() +``` + +For ordinary `SELECT`s prefer `client.query({ format })` — `default_format` is +only needed for raw `exec()`. + +## Common pitfalls + +- **Don't put a path in `url` and expect it to be the database name when + you're behind a proxy.** Use `pathname` for the proxy path and `database` + for the DB. (Symptom: "wrong database selected.") See the + troubleshooting skill for diagnosis. +- **Don't create a client per request.** `createClient` opens a connection + pool; share one client across the process and `close()` on shutdown. +- **`max_open_connections` must be `>= 1`** when set explicitly. diff --git a/skills/clickhouse-js-node-coding/reference/custom-json.md b/skills/clickhouse-js-node-coding/reference/custom-json.md new file mode 100644 index 000000000..2e6e4d242 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/custom-json.md @@ -0,0 +1,149 @@ +# Custom JSON `parse` / `stringify` + +> **Requires:** client `>= 1.14.0` (configurable `json.parse` and +> `json.stringify`). Earlier versions cannot swap the JSON implementation. + +Backing example: +[`examples/node/coding/custom_json_handling.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/custom_json_handling.ts). + +## Answer checklist + +When the user wants `UInt64`/`Int64` values back as `BigInt`: + +- State that configurable `json.parse` / `json.stringify` requires + `@clickhouse/client >= 1.14.0`. +- Show the supported `createClient({ json: { parse, stringify } })` option, + usually with `json-bigint` and `useNativeBigInt: true`. +- Combine it with `output_format_json_quote_64bit_integers: 0` so the server + emits unquoted 64-bit integers that the parser can turn into `BigInt`. +- Mention that `output_format_json_quote_64bit_integers: 0` is the default + since ClickHouse `25.8`, but setting it explicitly is useful for older + servers or portable examples. +- Warn that casting to JavaScript `Number` / `parseInt` / `parseFloat` loses + precision above `Number.MAX_SAFE_INTEGER`. + +## Why customize? + +The default `JSON.stringify` / `JSON.parse`: + +- Throws on `BigInt`. +- Calls `Date.prototype.toJSON()` (ISO string) — fine for `DateTime` with + `date_time_input_format: 'best_effort'`, surprising in some workflows. +- Loses precision for 64-bit integers returned as numbers (a separate + issue — covered in the troubleshooting skill). + +A custom `{ parse, stringify }` lets you plug in `JSONBig`, +`safe-stable-stringify`, your own `BigInt`-aware serializer, etc. + +## Recipe: BigInt-safe stringify, custom Date handling + +```ts +import { createClient } from '@clickhouse/client' + +const valueSerializer = (value: unknown): unknown => { + // Serialize Date as a UNIX millis number (instead of toJSON's ISO string) + if (value instanceof Date) { + return value.getTime() + } + + // Serialize BigInt as a string so JSON.stringify won't throw + if (typeof value === 'bigint') { + return value.toString() + } + + if (Array.isArray(value)) { + return value.map(valueSerializer) + } + + if (typeof value === 'object' && value !== null) { + return Object.fromEntries( + Object.entries(value).map(([k, v]) => [k, valueSerializer(v)]), + ) + } + + return value +} + +const client = createClient({ + json: { + parse: JSON.parse, + stringify: (obj: unknown) => JSON.stringify(valueSerializer(obj)), + }, +}) + +await client.command({ + query: ` + CREATE OR REPLACE TABLE inserts_custom_json_handling + (id UInt64, dt DateTime64(3, 'UTC')) + ENGINE MergeTree + ORDER BY id + `, +}) + +await client.insert({ + table: 'inserts_custom_json_handling', + format: 'JSONEachRow', + values: [ + { + id: BigInt(250000000000000200), // serialized as a string + dt: new Date(), // serialized as ms since epoch + }, + ], +}) + +const rows = await client.query({ + query: 'SELECT * FROM inserts_custom_json_handling', + format: 'JSONEachRow', +}) +console.info(await rows.json()) +await client.close() +``` + +> The custom `valueSerializer` runs **before** `JSON.stringify`, so values +> are transformed before the standard hooks (`Date.prototype.toJSON`, +> object `toJSON()` methods, etc.) ever run. + +## Recipe: BigInt-safe parsing for 64-bit integer columns + +If you want `UInt64`/`Int64` to come back as `BigInt`s (instead of strings +or precision-lossy numbers), plug in a `BigInt`-aware parser such as +[`json-bigint`](https://www.npmjs.com/package/json-bigint): + +```ts +import { createClient } from '@clickhouse/client' +import JSONBig from 'json-bigint' + +const bigJson = JSONBig({ useNativeBigInt: true }) + +const client = createClient({ + json: { + parse: bigJson.parse, + stringify: bigJson.stringify, + }, + clickhouse_settings: { + output_format_json_quote_64bit_integers: 0, + }, +}) +``` + +This applies to **both** outgoing JSON bodies and incoming JSON-format +responses. Combine with `output_format_json_quote_64bit_integers: 0` (the +default since CH 25.8) so the server emits unquoted 64-bit integers that +`json-bigint` can parse to `BigInt`. + +## Common pitfalls + +- **Setting `json.parse` only.** That only affects reading JSON responses; + outgoing JSON bodies use `json.stringify`. If you want consistent custom + handling in both directions, generally provide a matching `stringify` too. +- **Forgetting `bigint` handling in `stringify`.** Default `JSON.stringify` + throws on `BigInt`; if your data ever contains one, the insert will fail + with `TypeError: Do not know how to serialize a BigInt`. +- **Targeting client `< 1.14.0`.** The `json` option doesn't exist; you'll + need to convert values manually before calling `insert()` / `query()` (or + upgrade). +- **Casting 64-bit integers to `Number`.** JavaScript's `number` type has + only 53 bits of mantissa — values above `Number.MAX_SAFE_INTEGER` (2^53 − 1) + are silently rounded. Do **not** try to fix precision loss by calling + `Number()`, `parseInt()`, or `parseFloat()` on the value. The correct fix + is a `BigInt`-aware parser (shown above), not a lossy cast. diff --git a/skills/clickhouse-js-node-coding/reference/data-types.md b/skills/clickhouse-js-node-coding/reference/data-types.md new file mode 100644 index 000000000..0e09616c5 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/data-types.md @@ -0,0 +1,169 @@ +# Modern Data Types: Dynamic, Variant, JSON, Time, Time64 + +> **Applies to** (server side): +> +> - `Variant`: ClickHouse `>= 24.1`. +> - `Dynamic`: ClickHouse `>= 24.5`. +> - New `JSON` (object) type: ClickHouse `>= 24.8`. +> - All three are **no longer experimental since `25.3`**; on older servers, +> you must enable the corresponding `allow_experimental_*_type` setting. +> - `Time` / `Time64`: ClickHouse `>= 25.6` and require +> `enable_time_time64_type: 1`. + +Backing examples: +[`examples/node/coding/dynamic_variant_json.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/dynamic_variant_json.ts), +[`examples/node/coding/time_time64.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/time_time64.ts). + +## Answer checklist + +When answering about storing and reading JSON objects: + +- Use the new `JSON` column type, introduced in ClickHouse `>= 24.8`. +- Say `JSON` is no longer experimental since ClickHouse `25.3`; on older + supported versions, enable `allow_experimental_json_type`. +- Insert real JS objects with `format: 'JSONEachRow'`; do not + `JSON.stringify()` the column value. +- Read with a JSON output format such as `JSONEachRow` and `resultSet.json()`; + `JSON` column values come back as parsed JS objects. + +## `Dynamic`, `Variant(...)`, `JSON` + +```ts +import { createClient } from '@clickhouse/client' + +const client = createClient({ + // Required only on ClickHouse < 25.3 — harmless to leave on + clickhouse_settings: { + allow_experimental_variant_type: 1, + allow_experimental_dynamic_type: 1, + allow_experimental_json_type: 1, + }, +}) + +await client.command({ + query: ` + CREATE OR REPLACE TABLE chjs_dynamic_variant_json + ( + id UInt64, + var Variant(Int64, String), + dynamic Dynamic, + json JSON + ) + ENGINE MergeTree + ORDER BY id + `, +}) + +await client.insert({ + table: 'chjs_dynamic_variant_json', + format: 'JSONEachRow', + values: [ + { id: 1, var: 42, dynamic: 'foo', json: { foo: 'x' } }, + { id: 2, var: 'str', dynamic: 144, json: { bar: 10 } }, + ], +}) + +const rs = await client.query({ + query: ` + SELECT *, + variantType(var), + dynamicType(dynamic), + dynamicType(json.foo), + dynamicType(json.bar) + FROM chjs_dynamic_variant_json + `, + format: 'JSONEachRow', +}) +console.log(await rs.json()) +``` + +### Notes + +- The `JSON` column type accepts a real JS object on insert and returns one + on select — no need for `JSON.stringify` / `JSON.parse` in your app code. +- A JS number written into a `Dynamic` or `Variant` column defaults to + `Int64` on the server. In JSON formats, `output_format_json_quote_64bit_integers` + controls how 64-bit integers are returned: `1` returns them as JSON strings, + while `0` returns them as JSON numbers (and `0` is the default since CH `25.8`). + In JS, large 64-bit integers returned as numbers can lose precision, so use + quoted output if you need exact integer values in application code. +- Use `variantType(...)`, `dynamicType(...)` to introspect what the server + ended up storing. + +## `Time` and `Time64(p)` + +`Time` is signed seconds (`-999:59:59` … `999:59:59`). `Time64(p)` adds +sub-second precision (`p` digits, up to `9` for nanoseconds). Both require +`enable_time_time64_type: 1` on `>= 25.6`. + +```ts +const client = createClient({ + clickhouse_settings: { enable_time_time64_type: 1 }, +}) + +await client.command({ + query: ` + CREATE OR REPLACE TABLE chjs_time_time64 + ( + id UInt64, + t Time, + t64_0 Time64(0), + t64_3 Time64(3), + t64_6 Time64(6), + t64_9 Time64(9), + ) + ENGINE MergeTree + ORDER BY id + `, +}) + +await client.insert({ + table: 'chjs_time_time64', + format: 'JSONEachRow', + values: [ + { + id: 1, + t: '12:34:56', + t64_0: '12:34:56', + t64_3: '12:34:56.123', + t64_6: '12:34:56.123456', + t64_9: '12:34:56.123456789', + }, + { + id: 2, + t: '999:59:59', + t64_0: '999:59:59', + t64_3: '999:59:59.999', + t64_6: '999:59:59.999999', + t64_9: '999:59:59.999999999', + }, + { + id: 3, + t: '-999:59:59', + t64_0: '-999:59:59', + t64_3: '-999:59:59.999', + t64_6: '-999:59:59.999999', + t64_9: '-999:59:59.999999999', + }, + ], +}) +``` + +### Notes + +- Pass values as **strings** in the `HH:MM:SS[.fraction]` format. Negatives + are supported; the magnitude can exceed 24 hours. +- For `Time64(p)` with `p > 3`, do not use JS `Date` — it tops out at + millisecond precision and will silently truncate. + +## Common pitfalls + +- **Targeting old ClickHouse servers without the `allow_experimental_*` + setting.** On `< 25.3`, `CREATE TABLE` will fail without them. +- **Expecting `JSON`-column reads to be raw strings.** They come back as + parsed objects in JSON formats. +- **Inserting `Time64(9)` from JS `Date` and losing precision.** Use a + string instead. +- **Reading a `Variant`/`Dynamic` value of type `Int64` and being surprised + it's a string.** That's the standard 64-bit-integers-in-JSON behavior; + see the troubleshooting skill if you need to change it. diff --git a/skills/clickhouse-js-node-coding/reference/insert-columns.md b/skills/clickhouse-js-node-coding/reference/insert-columns.md new file mode 100644 index 000000000..79d6a22b3 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/insert-columns.md @@ -0,0 +1,113 @@ +# Insert into Specific Columns / Other Databases + +> **Applies to:** all versions. The `columns` option (both forms) and the +> `database` config field are universally supported. + +Backing examples: +[`examples/node/coding/insert_specific_columns.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_specific_columns.ts), +[`examples/node/coding/insert_exclude_columns.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_exclude_columns.ts), +[`examples/node/coding/insert_ephemeral_columns.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_ephemeral_columns.ts), +[`examples/node/coding/insert_into_different_db.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_into_different_db.ts). + +## Answer checklist + +When explaining partial-column inserts: + +- Show `columns: ['col_a', 'col_b']` for the allowlist form. +- Also mention the inverse `columns: { except: ['col_to_skip'] }` form so the + user knows both supported shapes. +- Explain that omitted columns receive their server-side defaults + (`DEFAULT`, `MATERIALIZED`, `ALIAS`, nullable/type defaults) and inserts can + still fail or produce surprising zero/empty values if the table definition + has no appropriate defaults. + +## Insert into specific columns + +Pass `columns: string[]` to limit the `INSERT` to a subset. Omitted columns +get their declared default. + +```ts +await client.insert({ + table: 'events', + format: 'JSONEachRow', + values: [{ message: 'foo' }], + columns: ['message'], // `id` will get its default (0 for UInt32) +}) +``` + +## Insert excluding columns + +Use `columns: { except: string[] }` for the inverse. Useful when most columns +should default but you want to name only the few to skip. + +```ts +await client.insert({ + table: 'events', + format: 'JSONEachRow', + values: [{ message: 'bar' }], + columns: { except: ['id'] }, +}) +``` + +## Tables with EPHEMERAL columns + +[Ephemeral columns](https://clickhouse.com/docs/en/sql-reference/statements/create/table#ephemeral) +are not stored — they only exist to drive `DEFAULT` expressions of other +columns. To trigger that default logic, **the ephemeral column must be in the +`columns` list**, even though no value will be persisted for it. + +```ts +await client.command({ + query: ` + CREATE OR REPLACE TABLE events + ( + id UInt64, + message String DEFAULT message_default, + message_default String EPHEMERAL + ) + ENGINE MergeTree + ORDER BY id + `, +}) + +await client.insert({ + table: 'events', + format: 'JSONEachRow', + values: [ + { id: '42', message_default: 'foo' }, + { id: '144', message_default: 'bar' }, + ], + // Including the ephemeral column name triggers the DEFAULT expression + columns: ['id', 'message_default'], +}) +``` + +## Insert into a different database + +If the client's default `database` is not the target, qualify the table name +with `db.table`: + +```ts +const client = createClient({ database: 'system' }) + +await client.command({ query: 'CREATE DATABASE IF NOT EXISTS analytics' }) + +await client.insert({ + table: 'analytics.events', // fully qualified + format: 'JSONEachRow', + values: [{ id: 42, message: 'foo' }], +}) +``` + +There is no per-call `database` override on `insert()` / `query()` — qualify +the identifier, or create a second client with the desired `database`. + +## Common pitfalls + +- **Forgetting the ephemeral column in `columns`.** If you list only the + non-ephemeral columns, the `DEFAULT` expression that depends on the + ephemeral value won't fire and you'll get empty/zero defaults instead. +- **Hoping `client.insert({ database: '…' })` works.** It doesn't — qualify + the `table` instead. +- **Mixing the two `columns` forms.** Use either `string[]` _or_ + `{ except: string[] }`, not both. diff --git a/skills/clickhouse-js-node-coding/reference/insert-formats.md b/skills/clickhouse-js-node-coding/reference/insert-formats.md new file mode 100644 index 000000000..0feb6c205 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/insert-formats.md @@ -0,0 +1,145 @@ +# Insert Data Formats + +> **Applies to:** all versions. The `JSON` type column / new JSON family is a +> ClickHouse feature; the JSON _formats_ listed here are universally supported +> by the client. + +Backing examples: +[`examples/node/coding/array_json_each_row.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/array_json_each_row.ts), +[`examples/node/coding/insert_data_formats_overview.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_data_formats_overview.ts). + +> **Raw / binary formats (CSV, TSV, CustomSeparated, Parquet) require a Node +> stream as input.** See +> [`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance) +> — defer if the user wants to insert from a file or `Readable`. + +## Answer checklist + +When answering "what format/call should I use for an array of JS objects?": + +- Use `client.insert({ table, values, format: 'JSONEachRow' })`. +- Say the array of plain objects can be passed directly as `values` for + ordinary in-memory batches such as a few thousand or tens of thousands of + rows. +- Do not steer the user to streaming, Parquet, or file APIs unless their input + is already a stream/file or the task is explicitly about throughput. +- Warn not to wrap `JSONEachRow` rows in a `{ data: [...] }` envelope; that + shape belongs to single-document formats. +- Mention `JSONCompactEachRow*` as a denser alternative for larger payloads + when the caller can provide positional arrays or explicit names/types. + +## Default choice: `JSONEachRow` with an array of objects + +This is the right answer for ~90% of inserts. + +```ts +import { createClient } from '@clickhouse/client' + +const client = createClient() + +await client.insert({ + table: 'events', + format: 'JSONEachRow', + values: [ + { id: 42, name: 'foo' }, + { id: 43, name: 'bar' }, + ], +}) + +await client.close() +``` + +The shape of `values` must match the chosen format. + +## Streamable JSON formats (pass an array) + +| Format | `values` shape | +| -------------------------------------------- | --------------------------------------------------- | +| `JSONEachRow` | `Array<{ col: value, ... }>` | +| `JSONStringsEachRow` | `Array<{ col: stringifiedValue, ... }>` | +| `JSONCompactEachRow` | `Array<[v1, v2, ...]>` | +| `JSONCompactStringsEachRow` | `Array<[stringV1, stringV2, ...]>` | +| `JSONCompactEachRowWithNames` | First row = column names, then data rows | +| `JSONCompactEachRowWithNamesAndTypes` | Row 1 = names, row 2 = types, then data | +| `JSONCompactStringsEachRowWithNames` | First row = names, then stringified data rows | +| `JSONCompactStringsEachRowWithNamesAndTypes` | Row 1 = names, row 2 = types, then stringified data | + +```ts +await client.insert({ + table: 'events', + format: 'JSONCompactEachRowWithNamesAndTypes', + values: [ + ['id', 'name', 'sku'], + ['UInt32', 'String', 'Array(UInt32)'], + [11, 'foo', [1, 2, 3]], + [12, 'bar', [4, 5, 6]], + ], +}) +``` + +These formats can be **streamed** — pass a Node stream of rows instead of an +array. See +[`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance) +for streaming guidance. + +## Single-document JSON formats (pass an object) + +These cannot be streamed — the entire body is sent in one shot. + +| Format | `values` shape (typed via `InputJSON` / `InputJSONObjectEachRow`) | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `JSON` | `{ meta: [], data: Array<{ col: value, ... }> }` — for TypeScript/client usage, pass `meta: []` if metadata is not needed | +| `JSONCompact` | `{ meta: [{ name, type }, ...], data: Array<[v1, v2, ...]> }` | +| `JSONColumnsWithMetadata` | `{ meta: [...], data: { col1: [v, ...], col2: [v, ...] } }` | +| `JSONObjectEachRow` | `Record` (the record key labels each row but is not stored) | + +```ts +import type { InputJSON, InputJSONObjectEachRow } from '@clickhouse/client' + +const meta: InputJSON['meta'] = [ + { name: 'id', type: 'UInt32' }, + { name: 'name', type: 'String' }, +] + +await client.insert({ + table: 'events', + format: 'JSONCompact', + values: { + meta, + data: [ + [19, 'foo'], + [20, 'bar'], + ], + }, +}) + +await client.insert({ + table: 'events', + format: 'JSONObjectEachRow', + values: { + row_1: { id: 23, name: 'foo' }, + row_2: { id: 24, name: 'bar' }, + } satisfies InputJSONObjectEachRow<{ id: number; name: string }>, +}) +``` + +## Quick chooser + +| Use case | Format | +| -------------------------------------------- | ------------------------------------------------- | +| Insert plain JS objects | `JSONEachRow` _(default)_ | +| Insert tuples / column-positional rows | `JSONCompactEachRow` | +| Insert with explicit column ordering / types | `JSONCompactEachRow*WithNames…` | +| Insert a single document with metadata | `JSON`, `JSONCompact` | +| Insert from a CSV / TSV / Parquet file | Raw format + Node stream → `examples/node/performance/` | + +## Common pitfalls + +- **Wrong shape for the format.** The most common cause of insert failures — + e.g., passing `Array<{...}>` to `JSONCompact` (which expects + `{ meta, data }`). +- **Don't wrap a `JSONEachRow` array in a `{ data: [...] }` envelope.** That + envelope only belongs to single-document formats (`JSON` / `JSONCompact` / + `JSONColumnsWithMetadata`). +- For type guidance (`Decimal` strings, `Date` objects, `BigInt`), see + `insert-values.md` and `custom-json.md`. diff --git a/skills/clickhouse-js-node-coding/reference/insert-values.md b/skills/clickhouse-js-node-coding/reference/insert-values.md new file mode 100644 index 000000000..6875f05ba --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/insert-values.md @@ -0,0 +1,141 @@ +# Insert Values, SQL Expressions, Dates, Decimals + +> **Applies to:** all versions. `wait_end_of_query: 1` is a server-side +> setting available on every supported ClickHouse version. + +Backing examples: +[`examples/node/coding/insert_from_select.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_from_select.ts), +[`examples/node/coding/insert_values_and_functions.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_values_and_functions.ts), +[`examples/node/coding/insert_js_dates.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_js_dates.ts), +[`examples/node/coding/insert_decimals.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/insert_decimals.ts). + +## `INSERT … SELECT` (no values payload) + +When the data already lives in ClickHouse, use `client.command()` with a raw +`INSERT … SELECT`: + +```ts +await client.command({ + query: ` + INSERT INTO target + SELECT '42', quantilesBFloat16State(0.5)(arrayJoin([toFloat32(10), toFloat32(20)])) + `, +}) +``` + +Use `command()` (not `insert()`) — there is no row payload to send. + +## `INSERT … VALUES` with SQL functions + +When you need `unhex(...)`, `toUUID(...)`, `now()`, or any other SQL +function around a value, keep the SQL shape static and pass values with +ClickHouse `{name: Type}` parameters. Run it via `command()` and set +`wait_end_of_query: 1` for safety in clustered setups. + +```ts +await client.command({ + query: ` + INSERT INTO events (id, timestamp, email, name) + VALUES ( + unhex({id: String}), + {timestamp: DateTime}, + {email: String}, + {name: Nullable(String)} + ) + `, + query_params: { + id: '00112233445566778899aabbccddeeff', + timestamp: '2026-05-06 12:34:56', + email: 'alice@example.com', + name: 'Alice', + }, + clickhouse_settings: { wait_end_of_query: 1 }, +}) +``` + +Do not build `VALUES` rows with string interpolation or manual escaping. If +you need to insert many ordinary JS rows, prefer `client.insert()` with +`format: 'JSONEachRow'`; use this `command()` pattern when the SQL itself needs +functions or expressions around the values. + +## Inserting JS `Date` objects + +JS `Date` objects work for `DateTime` and `DateTime64` columns once the +server is set to accept ISO-8601 strings. Either set +`date_time_input_format: 'best_effort'` per request, on the client, or +session-wide. + +```ts +await client.insert({ + table: 'events', + format: 'JSONEachRow', + values: [{ id: '42', dt: new Date() }], + clickhouse_settings: { + date_time_input_format: 'best_effort', + }, +}) +``` + +> JS `Date` objects do **not** work for the `Date` type (date-only) — pass +> `'YYYY-MM-DD'` strings for that. + +## Inserting `Decimal*` values + +Decimals must be passed as **strings** in JSON formats to avoid precision +loss in JavaScript: + +```ts +await client.command({ + query: ` + CREATE OR REPLACE TABLE prices ( + id UInt32, + dec32 Decimal(9, 2), + dec64 Decimal(18, 3), + dec128 Decimal(38, 10), + dec256 Decimal(76, 20) + ) + ENGINE MergeTree ORDER BY id + `, +}) + +await client.insert({ + table: 'prices', + format: 'JSONEachRow', + values: [ + { + id: 1, + dec32: '1234567.89', + dec64: '123456789123456.789', + dec128: '1234567891234567891234567891.1234567891', + dec256: + '12345678912345678912345678911234567891234567891234567891.12345678911234567891', + }, + ], +}) +``` + +When reading them back, cast to string in the SELECT to avoid the same +precision loss: + +```ts +const rs = await client.query({ + query: ` + SELECT toString(dec64) AS decimal64, + toString(dec128) AS decimal128 + FROM prices + `, + format: 'JSONEachRow', +}) +``` + +## Common pitfalls + +- **Passing decimals as JS `number`s.** Anything beyond `Number.MAX_SAFE_INTEGER` + silently loses precision before it ever reaches the server. +- **Using `client.insert()` for `INSERT … SELECT`.** There's nothing to + upload — use `client.command()` with the full SQL. +- **Forgetting `date_time_input_format: 'best_effort'`** when inserting + `Date` objects (or ISO strings). The default input format does not accept + ISO-8601 with the `T`/`Z` separators. +- **Hand-building `VALUES` with user input.** Always parameterize user data; + see `reference/query-parameters.md`. diff --git a/skills/clickhouse-js-node-coding/reference/ping.md b/skills/clickhouse-js-node-coding/reference/ping.md new file mode 100644 index 000000000..8069ae3fb --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/ping.md @@ -0,0 +1,120 @@ +# Ping the Server + +> **Applies to:** all versions. `ping()` returns a discriminated +> `PingResult = { success: true } | { success: false, error: Error }` — +> it does **not** throw on connection failures. + +Backing examples: +[`examples/node/coding/ping_existing_host.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/ping_existing_host.ts), +[`examples/node/coding/ping_non_existing_host.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/ping_non_existing_host.ts). + +## Successful ping + +```ts +import { createClient } from '@clickhouse/client' + +const client = createClient({ + url: process.env.CLICKHOUSE_URL, + password: process.env.CLICKHOUSE_PASSWORD, +}) + +const pingResult = await client.ping() +if (pingResult.success) { + console.info('ClickHouse is reachable') +} else { + console.error('Ping failed:', pingResult.error) +} +await client.close() +``` + +Use `ping()` to: + +- Probe ClickHouse at application startup. +- Wake up a ClickHouse Cloud instance that may be idling (a ping is enough to + bring it out of sleep). +- Implement a `/healthz` / readiness endpoint. + +## Failure: host unreachable + +`ping()` does **not** throw — it resolves with +`{ success: false, error: Error }`, so you can branch without `try/catch`: + +```ts +import type { PingResult } from '@clickhouse/client' +import { createClient } from '@clickhouse/client' + +const client = createClient({ + url: 'http://localhost:8100', // non-existing host + request_timeout: 50, // keep failure fast +}) + +const pingResult = await client.ping() +if (hasConnectionRefusedError(pingResult)) { + console.info('Connection refused, as expected') +} else { + console.error('Ping expected ECONNREFUSED, got:', pingResult) +} +await client.close() + +function hasConnectionRefusedError( + pingResult: PingResult, +): pingResult is PingResult & { error: { code: 'ECONNREFUSED' } } { + return ( + !pingResult.success && + 'code' in pingResult.error && + pingResult.error.code === 'ECONNREFUSED' + ) +} +``` + +## Mapping to an HTTP health endpoint + +```ts +app.get('/healthz', async (_req, res) => { + const r = await client.ping() + if (r.success) { + res.status(200).json({ ok: true }) + } else { + res.status(503).json({ ok: false, error: String(r.error) }) + } +}) +``` + +## `ping()` vs `ping({ select: true })` + +The default `ping()` hits ClickHouse's `/ping` HTTP endpoint — it verifies +network connectivity but **does not check credentials or query processing**. +A server that is reachable but has a bad password (or a broken query +pipeline) will still return `{ success: true }` from a plain `ping()`. + +Pass `{ select: true }` to run a lightweight `SELECT 1` instead: + +```ts +const r = await client.ping({ select: true }) +// success only if the server is reachable AND auth is correct AND it can run queries +``` + +| | `client.ping()` | `client.ping({ select: true })` | +| ----------------------- | --------------- | ------------------------------- | +| Endpoint | `/ping` (HTTP) | `SELECT 1` query | +| Checks auth | **No** | Yes | +| Checks query processing | No | **Yes** | +| Overhead | Minimal | Slightly higher | + +**When to use which:** + +- **Liveness probe** (is the process alive?) — plain `ping()` is fine. +- **Readiness probe** (can it serve traffic?) — use `ping({ select: true })` + so the probe fails if credentials are wrong or the query layer is broken. +- **Waking a ClickHouse Cloud idle instance** — plain `ping()` is enough. + +## Common pitfalls + +- **Do not wrap `ping()` in `try/catch` as your only check.** It resolves on + failure; the `success` boolean is the source of truth. +- **Lower `request_timeout` if you want pings to fail fast** (the example + above uses `50` ms). The default is high enough to be unsuitable for + liveness probes. +- **Plain `ping()` does not check credentials.** If auth is part of what you + want to verify, use `ping({ select: true })`. +- For ping that times out specifically, see the troubleshooting skill. diff --git a/skills/clickhouse-js-node-coding/reference/query-parameters.md b/skills/clickhouse-js-node-coding/reference/query-parameters.md new file mode 100644 index 000000000..ca9e35788 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/query-parameters.md @@ -0,0 +1,152 @@ +# Query Parameter Binding + +> **Applies to:** all versions. NULL parameter binding fixed in `0.0.16`. +> Special-character (tab/newline/quote/backslash) binding `>= 0.3.1`. +> `TupleParam` and JS `Map` parameters `>= 1.9.0`. Boolean formatting in +> `Array`/`Tuple`/`Map` parameters fixed in `>= 1.13.0`. `BigInt` query +> parameters `>= 1.15.0`. + +Backing examples: +[`examples/node/coding/query_with_parameter_binding.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/query_with_parameter_binding.ts), +[`examples/node/coding/query_with_parameter_binding_special_chars.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/query_with_parameter_binding_special_chars.ts). + +## Answer checklist + +When the user passes user-controlled values into SQL: + +- Use ClickHouse `{name: Type}` placeholders and a `query_params` object. +- Explicitly call template-literal/string interpolation of user input a + **SQL injection risk**. +- Do not suggest PostgreSQL/MySQL-style `$1`, `?`, or `:name` placeholders. +- Pick the placeholder type to match the ClickHouse column type (`String`, + `Date`, `DateTime`, `Nullable(T)`, etc.). + +## Syntax: `{name: Type}` + +ClickHouse uses `{name: Type}` placeholders — **not** `$1`, `?`, or `:name`. + +```ts +await client.query({ + query: 'SELECT plus({a: Int32}, {b: Int32})', + format: 'JSONEachRow', + query_params: { a: 10, b: 20 }, +}) +``` + +The `Type` must be a valid ClickHouse type (`Int32`, `String`, `Date`, +`Array(UInt32)`, `Tuple(Int32, String)`, `Map(K, V)`, `Nullable(T)`, etc.). + +## ⚠️ Never use template literals for user values + +Interpolating user input into the SQL string bypasses server-side escaping +and opens the door to SQL injection: + +```ts +// ❌ Dangerous — never do this with user-controlled values +const userId = req.params.id +await client.query({ query: `SELECT * FROM users WHERE id = ${userId}` }) + +// ✓ Safe — parameterized +await client.query({ + query: 'SELECT * FROM users WHERE id = {id: UInt32}', + query_params: { id: userId }, +}) +``` + +This is the most common mistake for users coming from PostgreSQL/MySQL. Call +it out explicitly when the user shows template-literal interpolation. + +## Common types + +```ts +import { TupleParam } from '@clickhouse/client' + +await client.query({ + query: ` + SELECT + {var_int: Int32} AS var_int, + {var_float: Float32} AS var_float, + {var_str: String} AS var_str, + {var_array: Array(Int32)} AS var_array, + {var_tuple: Tuple(Int32, String)} AS var_tuple, + {var_map: Map(Int, Array(String))} AS var_map, + {var_date: Date} AS var_date, + {var_datetime: DateTime} AS var_datetime, + {var_datetime64_3: DateTime64(3)} AS var_datetime64_3, + {var_datetime64_9: DateTime64(9)} AS var_datetime64_9, + {var_decimal: Decimal(9, 2)} AS var_decimal, + {var_uuid: UUID} AS var_uuid, + {var_ipv4: IPv4} AS var_ipv4, + {var_null: Nullable(String)} AS var_null + `, + format: 'JSONEachRow', + query_params: { + var_int: 10, + var_float: '10.557', + var_str: 'hello', + var_array: [42, 144], + var_tuple: new TupleParam([42, 'foo']), // >= 1.9.0 + var_map: new Map([ + [42, ['a', 'b']], + [144, ['c', 'd']], + ]), // >= 1.9.0 + var_date: '2022-01-01', + var_datetime: '2022-01-01 12:34:56', // or a Date + var_datetime64_3: '2022-01-01 12:34:56.789', // or a Date + var_datetime64_9: '2022-01-01 12:34:56.123456789', // string for ns precision + var_decimal: '123.45', // string to avoid precision loss + var_uuid: '01234567-89ab-cdef-0123-456789abcdef', + var_ipv4: '192.168.0.1', + var_null: null, // fixed in 0.0.16 + }, +}) +``` + +### Type-by-type tips + +- **Decimals** — pass as strings to avoid JS number precision loss. +- **`DateTime64(>3)`** — pass as a string; JS `Date` only has millisecond + precision and will lose sub-millisecond digits. +- **`DateTime64`** — strings can also be UNIX timestamps, including + fractional ones (e.g., `'1651490755.123456789'`). +- **`BigInt`** — supported in `query_params` since `>= 1.15.0`. On older + clients, pass as a string. +- **`Tuple(...)`** — wrap in `new TupleParam([...])` (`>= 1.9.0`); on older + clients, build the literal manually as a string. +- **`Map(K, V)`** — pass a JS `Map` (`>= 1.9.0`); on older clients, build + it manually. +- **`Nullable(T)`** — pass `null` directly (`>= 0.0.16`). + +## Special characters in string parameters (`>= 0.3.1`) + +Tabs, newlines, carriage returns, single quotes, and backslashes are +escaped automatically by the client — just pass the JS string as-is: + +```ts +await client.query({ + query: ` + SELECT + 'foo_\t_bar' = {tab: String} AS has_tab, + 'foo_\n_bar' = {newline: String} AS has_newline, + 'foo_\\'_bar' = {single_quote: String} AS has_single_quote, + 'foo_\\_bar' = {backslash: String} AS has_backslash + `, + format: 'JSONEachRow', + query_params: { + tab: 'foo_\t_bar', + newline: 'foo_\n_bar', + single_quote: "foo_'_bar", + backslash: 'foo_\\_bar', + }, +}) +``` + +## Common pitfalls + +- **`$1` / `?` / `:name` placeholders.** None work — use `{name: Type}`. +- **Forgetting the type in the placeholder.** `{id}` is a syntax error; + it must be `{id: UInt32}`. +- **Stringifying tuples/maps manually on `>= 1.9.0`.** Use `TupleParam` + and `Map` — both serialize correctly and respect special characters. +- **Boolean array/tuple/map elements before `1.13.0`.** Boolean formatting + was fixed in 1.13.0 — earlier versions may misformat them. diff --git a/skills/clickhouse-js-node-coding/reference/select-formats.md b/skills/clickhouse-js-node-coding/reference/select-formats.md new file mode 100644 index 000000000..5a0305deb --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/select-formats.md @@ -0,0 +1,111 @@ +# Select Data Formats + +> **Applies to:** all versions. `JSONEachRowWithProgress` requires client +> `>= 1.7.0`; see the in-repo performance examples under +> `examples/node/performance/`. + +Backing examples: +[`examples/node/coding/select_json_each_row.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/select_json_each_row.ts), +[`examples/node/coding/select_data_formats_overview.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/select_data_formats_overview.ts), +[`examples/node/coding/select_json_with_metadata.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/select_json_with_metadata.ts). + +## Default choice: `JSONEachRow` → `.json()` + +Right answer for ~90% of selects when the result fits in memory. + +```ts +import { createClient } from '@clickhouse/client' + +interface Row { + number: string +} + +const client = createClient() +const rows = await client.query({ + query: 'SELECT number FROM system.numbers LIMIT 5', + format: 'JSONEachRow', +}) +const result = await rows.json() // Row[] +result.forEach((r) => console.log(r)) +await client.close() +``` + +`UInt64`/`Int64` and other 64-bit integers are returned as **strings** +when `output_format_json_quote_64bit_integers=1`, to avoid JS precision +loss. If that setting is `0`, they may be returned as unquoted JSON +numbers instead. Note that in ClickHouse `>= 25.8`, this setting can +default to `0`; see the troubleshooting skill for ways to control that. + +## Single-document `JSON` format with metadata + +Use `JSON` (or `JSONCompact`) when you need ClickHouse's response envelope +(rows + meta + statistics + row count). Type the result with +`ResponseJSON`: + +```ts +import { createClient, type ResponseJSON } from '@clickhouse/client' + +const client = createClient() +const rows = await client.query({ + query: 'SELECT number FROM system.numbers LIMIT 2', + format: 'JSON', +}) +const result = await rows.json>() +console.info(result.meta, result.data, result.rows, result.statistics) +await client.close() +``` + +> `JSON`, `JSONCompact`, `JSONStrings`, `JSONCompactStrings`, +> `JSONColumnsWithMetadata`, `JSONObjectEachRow` are **single-document** +> formats — they cannot be streamed. Use a `*EachRow` variant if you want +> to stream. + +## Selecting raw text (CSV / TSV / CustomSeparated) + +Use `.text()` (not `.json()`) for raw textual formats: + +```ts +const rs = await client.query({ + query: 'SELECT number, number * 2 AS doubled FROM system.numbers LIMIT 3', + format: 'CSVWithNames', +}) +console.log(await rs.text()) +``` + +Streaming raw text/Parquet line-by-line belongs in +[`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance) +— in particular, Parquet exports use `client.exec()` and pipe the raw +response stream rather than `ResultSet.stream()` (see +[`select_parquet_as_file.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/performance/select_parquet_as_file.ts)). + +## Format chooser + +| Use case | Format | +| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Read rows as JS objects | `JSONEachRow` _(default)_ | +| Read rows as positional tuples (smaller payload) | `JSONCompactEachRow` | +| Need `meta` / `statistics` / `rows` envelope | `JSON` or `JSONCompact` + `ResponseJSON` | +| Read all values as strings (avoid number-precision loss) | `JSONStringsEachRow` / `JSONCompactStringsEachRow` | +| Stream very large result | `JSONEachRow` / `JSONCompactEachRow` (see [`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance)) | +| Export to CSV/TSV/Parquet | `CSV*`, `TabSeparated*`, `Parquet` (see [`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance)) | + +## ResultSet methods + +| Method | Returns | Notes | +| -------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `await rs.json()` | `T[]` for `*EachRow`, single-doc shape otherwise | Buffers the full response | +| `await rs.text()` | `string` | Buffers the full response — for textual formats only (CSV/TSV/etc.) | +| `rs.stream()` | Node `Readable` of `Row[]` chunks | Use for large newline-delimited results (`JSONEachRow`/`JSONCompactEachRow`/`CSV`/`TSV`); **not** suitable for binary formats like `Parquet` — for those, use `client.exec()` and pipe the raw response stream (see [`examples/node/performance/`](https://github.com/ClickHouse/clickhouse-js/tree/main/examples/node/performance)) | +| `rs.close()` | `void` (synchronous) | Always call if you obtained `stream()` and stop reading early | + +## Common pitfalls + +- **Calling `.json()` on a `JSON` (single-doc) result and expecting an + array.** You get a `ResponseJSON` object; the rows are under + `.data`. Use `JSONEachRow` if you want a flat array. +- **Leaving a `stream()` half-consumed.** This is a top cause of + `ECONNRESET` on the _next_ request — fully iterate the stream or call + `resultSet.close()` (synchronous — no `await`). (Diagnosis details live in the + troubleshooting skill.) +- **Reaching for `.json()` on a CSV/TSV result.** Use `.text()` (or + `.stream()` for large results). diff --git a/skills/clickhouse-js-node-coding/reference/sessions.md b/skills/clickhouse-js-node-coding/reference/sessions.md new file mode 100644 index 000000000..630233755 --- /dev/null +++ b/skills/clickhouse-js-node-coding/reference/sessions.md @@ -0,0 +1,152 @@ +# Sessions and Temporary Tables + +> **Applies to:** all versions. `session_id` is a server-level concept; the +> client just forwards it on every request that names it. + +Backing examples: +[`examples/node/coding/session_id_and_temporary_tables.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/session_id_and_temporary_tables.ts), +[`examples/node/coding/session_level_commands.ts`](https://github.com/ClickHouse/clickhouse-js/blob/main/examples/node/coding/session_level_commands.ts). + +## When you need a session + +Use a `session_id` whenever multiple calls must share **server-side state**: + +- `CREATE TEMPORARY TABLE` (the table only exists within its session). +- `SET = ` to apply for subsequent queries on the same + session. +- Any other server feature scoped per session (e.g., session-scoped + variables in newer ClickHouse versions). + +## ⚠️ `session_id` and concurrency + +ClickHouse **rejects concurrent queries within the same session** — if two +requests arrive at the server at the same time sharing the same `session_id`, +the second one gets an error like +`"Session is locked by a concurrent client"`. This has two practical +implications: + +1. **Do not set `session_id` on a global / module-static client** that handles + concurrent requests (e.g., an Express app's shared client). Every + in-flight request would share the same session and collide under load. +2. **If you do set `session_id` on a client**, restrict its concurrency: + set `max_open_connections: 1` so at most one request is in flight at a + time, turning the pool into a serial queue. This is fine for a + dedicated per-workflow client but wrong for a shared application client. + +The right pattern for application code: create a **short-lived client** (or +use per-request `session_id`) scoped to a single logical workflow, not to +the entire process. + +## Per-client `session_id` + +Appropriate when **one client handles exactly one sequential workflow** (a +script, a background job, a single user's session that you've already +serialized). + +```ts +import { createClient } from '@clickhouse/client' +import * as crypto from 'node:crypto' + +const client = createClient({ + session_id: crypto.randomUUID(), + max_open_connections: 1, // prevent concurrent-session errors +}) + +await client.command({ + query: 'CREATE TEMPORARY TABLE temporary_example (i Int32)', +}) + +await client.insert({ + table: 'temporary_example', + values: [{ i: 42 }, { i: 144 }], + format: 'JSONEachRow', +}) + +const rs = await client.query({ + query: 'SELECT * FROM temporary_example', + format: 'JSONEachRow', +}) +console.info(await rs.json()) +await client.close() +``` + +## Session-level `SET` commands + +`SET` only persists within a session. With `session_id` defined on the +client, every subsequent call inherits the change. + +```ts +import { createClient } from '@clickhouse/client' +import * as crypto from 'node:crypto' + +const client = createClient({ + session_id: crypto.randomUUID(), + max_open_connections: 1, // prevent concurrent-session errors +}) + +await client.command({ + query: 'SET output_format_json_quote_64bit_integers = 0', + clickhouse_settings: { wait_end_of_query: 1 }, // ack before next call +}) + +const rs1 = await client.query({ + query: 'SELECT toInt64(42)', + format: 'JSONEachRow', +}) +// → 64-bit integers come back as numbers in this query + +await client.command({ + query: 'SET output_format_json_quote_64bit_integers = 1', + clickhouse_settings: { wait_end_of_query: 1 }, +}) + +const rs2 = await client.query({ + query: 'SELECT toInt64(144)', + format: 'JSONEachRow', +}) +// → 64-bit integers come back as strings again + +await client.close() +``` + +> **`wait_end_of_query: 1` matters here.** Without it, a `SET` on one +> connection in the pool may not yet be applied when the next query lands +> on the same socket. + +## Per-request `session_id` + +You can also pass `session_id` on a single `query()` / `insert()` / +`command()` call to override (or set) it for that one request. + +## ⚠️ Sessions and load balancers / ClickHouse Cloud + +Sessions are bound to a **specific ClickHouse node**. If a load balancer in +front of ClickHouse routes consecutive requests to different nodes, the +temporary table / `SET` won't be visible — you'll get +`UNKNOWN_TABLE` / surprising results. + +Mitigations: + +- Talk to a single node directly. +- For ClickHouse Cloud, use [replica-aware + routing](https://clickhouse.com/docs/manage/replica-aware-routing). +- Avoid sessions for cross-node workflows; persist intermediate state in a + regular (non-temporary) table instead. + +## Common pitfalls + +- **Forgetting `session_id` and being surprised that + `CREATE TEMPORARY TABLE` "disappears."** Without a session, every request + may land on a different connection / server context. +- **Setting `session_id` on a shared application client.** Under concurrent + load, two in-flight requests will share the same session and one will fail + with `"Session is locked by a concurrent client"`. Use per-request + `session_id` or a dedicated short-lived client instead. +- **Reusing the same `session_id` across unrelated workflows.** A second + session-using consumer will trip over your temporary tables and `SET` + values. Generate a fresh UUID per logical session. +- **Leaving session state pinned for the lifetime of the process.** If + long-lived clients accumulate `SET` / temp-table state, consider creating + a short-lived sub-client with its own `session_id` for the unit of work. +- **Skipping `wait_end_of_query: 1` on `SET`** — race conditions between + `SET` and the next query can show up under load. diff --git a/skills/clickhouse-js-node-troubleshooting/reference/socket-hangup.md b/skills/clickhouse-js-node-troubleshooting/reference/socket-hangup.md index 91c14764d..39ffc7ee4 100644 --- a/skills/clickhouse-js-node-troubleshooting/reference/socket-hangup.md +++ b/skills/clickhouse-js-node-troubleshooting/reference/socket-hangup.md @@ -113,7 +113,7 @@ const client = createClient({ **Node.js defaults to a total received HTTP header limit of approximately 16 KB (this can be increased via the `--max-http-header-size` CLI flag[^max-header-size]).** ClickHouse sends a new progress header with each interval (~200 bytes), and after ~75 progress headers accumulate, Node.js will throw an exception and terminate the request unless that limit is raised. -[^max-header-size]: Node.js also exposes a `maxHeaderSize` option on `http(s).request`, but the ClickHouse JS client currently does not forward it through `createClient`. For now, the practical workaround in clickhouse-js is to either use the `--max-http-header-size` CLI flag / `NODE_OPTIONS` (process-wide) or supply a custom `http.Agent` configured with `maxHeaderSize`. A dedicated client option is coming soon. +[^max-header-size]: Since `>= 1.18.5`, the ClickHouse JS client also forwards a per-request limit via the `max_response_headers_size` (bytes) option on `createClient` (Node.js only — see the example below). On older versions, the practical workarounds are the `--max-http-header-size` CLI flag / `NODE_OPTIONS` (process-wide) or supplying a custom `http.Agent` configured with `maxHeaderSize`. **Maximum safe query duration formula:** @@ -133,11 +133,23 @@ Max duration (seconds) ≈ http_headers_progress_interval_ms × 75 ÷ 1000 If you need a longer max safe duration without lengthening the progress interval, raise Node's HTTP header limit. For example, increasing it from the default 16 KB to **64 KB** quadruples the max safe duration (≈300 progress headers instead of ≈75). +```ts +// Option 1 (recommended, since `>= 1.18.5`) — per-client, no process-wide flag needed +const client = createClient({ + request_timeout: 400_000, + max_response_headers_size: 65536, // 64 KB; lifts the per-request header cap + clickhouse_settings: { + send_progress_in_http_headers: 1, + http_headers_progress_interval_ms: '110000', + }, +}) +``` + ```bash -# Option 1 — CLI flag when launching your app +# Option 2 — CLI flag when launching your app (process-wide; older client versions) node --max-http-header-size=65536 app.js -# Option 2 — environment variable (works with any Node entry point, including npm/ts-node) +# Option 3 — environment variable (works with any Node entry point, including npm/ts-node) NODE_OPTIONS="--max-http-header-size=65536" node app.js ``` diff --git a/tests/clickhouse-test-runner/.gitignore b/tests/clickhouse-test-runner/.gitignore new file mode 100644 index 000000000..f690e4f90 --- /dev/null +++ b/tests/clickhouse-test-runner/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.log +.upstream/ diff --git a/tests/clickhouse-test-runner/README.md b/tests/clickhouse-test-runner/README.md new file mode 100644 index 000000000..bdd06271f --- /dev/null +++ b/tests/clickhouse-test-runner/README.md @@ -0,0 +1,114 @@ +# clickhouse-test-runner + +## What this is + +This package is a Node.js port of the Java +[`tests/clickhouse-client`](https://github.com/ClickHouse/clickhouse-java/tree/main/tests/clickhouse-client) +harness from [`ClickHouse/clickhouse-java`](https://github.com/ClickHouse/clickhouse-java). +It wraps [`@clickhouse/client`](../../packages/client-node) in a tiny CLI that +mimics the upstream `clickhouse-client` binary (same flags, same +`extract-from-config` shortcut, same stdin/`--query` behavior) so that the +official ClickHouse Python test runner +([`tests/clickhouse-test`](https://github.com/ClickHouse/ClickHouse/tree/master/tests)) +can drive a subset of the real ClickHouse SQL test suite against this Node.js +client. It lets us see exactly which upstream SQL tests pass or fail when run +through `@clickhouse/client`, without having to reimplement the test runner +itself. + +## Build + +```bash +cd tests/clickhouse-test-runner +npm install +npm run build +``` + +The build emits `dist/main.js`, which is the entry point used by the +`bin/clickhouse` shim. + +## Wrapper executable + +`bin/clickhouse` is a small Bash script that: + +- Handles the `extract-from-config --key …` shortcut directly in shell, since + the upstream Python runner spawns this synchronously during setup and only + ever asks for `listen_host` (we always answer `127.0.0.1`). +- Forwards every other invocation to `node dist/main.js` with the original + arguments. + +To make the official runner use this shim, prepend `bin/` to `PATH` **only in +the shell session that runs the tests** so you don't shadow a real +`clickhouse-client` binary you may have installed system-wide: + +```bash +export PATH="/path/to/clickhouse-js/tests/clickhouse-test-runner/bin:$PATH" +``` + +## Environment variables + +| Variable | Default | Description | +| --------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | +| `CLICKHOUSE_CLIENT_CLI_LOG` | `tests/clickhouse-test-runner/.upstream/clickhouse-client-cli.log` | Path to a log file used to record every shim invocation. Useful for troubleshooting. | +| `UPSTREAM_CLICKHOUSE_DIR` | `tests/clickhouse-test-runner/.upstream/ClickHouse` | Path to a checkout of `ClickHouse/ClickHouse` containing the upstream test suite. | +| `UPSTREAM_TEST_LIST` | `tests/clickhouse-test-runner/upstream-allowlist.txt` | Path to a file listing the upstream tests to run (one test name per line, `#` for comments). | +| `SHARD_INDEX` | `1` | 1-based index of the shard to run when sharding the allowlist (must be `<= SHARD_TOTAL`). | +| `SHARD_TOTAL` | `1` | Total number of shards. When `> 1`, only tests at positions where `i % SHARD_TOTAL == SHARD_INDEX - 1` are run (round-robin selection). | + +## Running against the upstream test suite + +To avoid a full multi-GB clone of `ClickHouse/ClickHouse`, use a sparse + shallow clone: + +```bash +git clone --depth 1 --filter=blob:none --sparse https://github.com/ClickHouse/ClickHouse.git +cd ClickHouse +git sparse-checkout set tests/clickhouse-test tests/queries tests/config tests/ci +``` + +Then run the helper script from the `clickhouse-js` repository: + +```bash +cd /path/to/clickhouse-js +UPSTREAM_CLICKHOUSE_DIR=/path/to/ClickHouse \ + tests/clickhouse-test-runner/scripts/run-upstream-tests.sh +``` + +The helper script reads the tests listed in `upstream-allowlist.txt` and runs them through the wrapper. It honors the environment variables documented in the [Environment variables](#environment-variables) table above, including `UPSTREAM_CLICKHOUSE_DIR` and `UPSTREAM_TEST_LIST`. + +Extra positional arguments are forwarded to `tests/clickhouse-test`. For example, to skip the stateful tests: + +```bash +UPSTREAM_CLICKHOUSE_DIR=/path/to/ClickHouse \ + tests/clickhouse-test-runner/scripts/run-upstream-tests.sh --no-stateful +``` + +### Extending the allowlist + +The file `upstream-allowlist.txt` contains the curated list of upstream tests known to pass through this harness. The file format is one test name per line; lines starting with `#` and blank lines are ignored. + +**Rule of thumb:** Only add tests that pass reliably. Remove or comment out tests that begin to flake. + +### CI + +The workflow `.github/workflows/upstream-sql-tests.yml` runs this harness in CI: + +- Triggered on **workflow_dispatch**, **nightly at 05:00 UTC**, and on **pushes/PRs** that touch `tests/clickhouse-test-runner/**` or the workflow file itself. +- The `workflow_dispatch` input `upstream_ref` can be used to pin a specific upstream commit or branch (defaults to `master`). +- The matrix runs every combination of `{clickhouse: head | latest} × {shard: 1..N}`. Sharding is round-robin across the allowlist (see `SHARD_INDEX` / `SHARD_TOTAL` above) so each shard takes roughly one minute. If the allowlist grows enough that per-shard runtime climbs back above ~1 minute, bump both the `shard` matrix values and the `SHARD_TOTAL` env value in the workflow together. + +## Local development + +From this directory: + +- `npm run build` — compile TypeScript to `dist/`. +- `npm run typecheck` — run `tsc --noEmit`. +- `npm run lint` — run ESLint with `--max-warnings=0`. +- `npm test` — run the Vitest unit suite (`__tests__/**/*.test.ts`). + +## Status + +This is a developer-facing harness. It is **not** an exhaustive +`clickhouse-client` replacement; only the flags and behaviors that +`tests/clickhouse-test` actually exercises are implemented. The +`SERVER_SETTINGS` and `CLIENT_ONLY_SETTINGS` allowlists in +[`src/settings.ts`](src/settings.ts) are copied from the Java port and may +need to be periodically resynced as ClickHouse adds or reclassifies settings. diff --git a/tests/clickhouse-test-runner/__tests__/args.test.ts b/tests/clickhouse-test-runner/__tests__/args.test.ts new file mode 100644 index 000000000..f5187c504 --- /dev/null +++ b/tests/clickhouse-test-runner/__tests__/args.test.ts @@ -0,0 +1,152 @@ +import { describe, expect, it } from 'vitest' +import { parseArgs } from '../src/args.js' +import { SERVER_SETTINGS } from '../src/settings.js' + +describe('parseArgs', () => { + it('returns defaults for an empty argv', () => { + const args = parseArgs([]) + expect(args.host).toBe('localhost') + expect(args.port).toBe(8123) + expect(args.user).toBe('default') + expect(args.password).toBe('') + expect(args.database).toBe('default') + expect(args.multiquery).toBe(false) + expect(args.secure).toBe(false) + expect(args.query).toBeNull() + expect(args.help).toBe(false) + expect(args.logComment).toBeNull() + expect(args.sendLogsLevel).toBeNull() + expect(args.maxInsertThreads).toBeNull() + expect(args.serverSettings).toEqual({}) + }) + + it('parses long-form options with separate values', () => { + const args = parseArgs([ + '--host', + 'other', + '--port', + '9000', + '--user', + 'u', + '--password', + 'p', + '--database', + 'd', + ]) + expect(args.host).toBe('other') + expect(args.port).toBe(9000) + expect(args.user).toBe('u') + expect(args.password).toBe('p') + expect(args.database).toBe('d') + }) + + it('parses long-form options with = form', () => { + const args = parseArgs([ + '--host=other', + '--port=9000', + '--user=u', + '--password=p', + '--database=d', + ]) + expect(args.host).toBe('other') + expect(args.port).toBe(9000) + expect(args.user).toBe('u') + expect(args.password).toBe('p') + expect(args.database).toBe('d') + }) + + it('parses short options including -q and -s', () => { + const args = parseArgs([ + '-h', + 'other', + '-u', + 'u', + '-d', + 'd', + '-q', + 'SELECT 1', + '-s', + ]) + expect(args.host).toBe('other') + expect(args.user).toBe('u') + expect(args.database).toBe('d') + expect(args.query).toBe('SELECT 1') + expect(args.secure).toBe(true) + }) + + it('accepts both --multiquery and --multi-query', () => { + expect(parseArgs(['--multiquery']).multiquery).toBe(true) + expect(parseArgs(['--multi-query']).multiquery).toBe(true) + }) + + it('silently accepts --multiline and -n', () => { + expect(() => parseArgs(['--multiline'])).not.toThrow() + expect(() => parseArgs(['-n'])).not.toThrow() + const a = parseArgs(['--multiline', '-n']) + expect(a.serverSettings).toEqual({}) + expect(a.query).toBeNull() + }) + + it('sets help=true for --help', () => { + expect(parseArgs(['--help']).help).toBe(true) + }) + + it('accepts both underscore and dash forms for log_comment', () => { + expect(parseArgs(['--log_comment', 'foo']).logComment).toBe('foo') + expect(parseArgs(['--log-comment', 'foo']).logComment).toBe('foo') + }) + + it('accepts both forms for send_logs_level', () => { + expect(parseArgs(['--send_logs_level', 'trace']).sendLogsLevel).toBe( + 'trace', + ) + expect(parseArgs(['--send-logs-level', 'trace']).sendLogsLevel).toBe( + 'trace', + ) + }) + + it('accepts both forms for max_insert_threads', () => { + expect(parseArgs(['--max_insert_threads', '8']).maxInsertThreads).toBe('8') + expect(parseArgs(['--max-insert-threads', '8']).maxInsertThreads).toBe('8') + }) + + it('routes --max_insert_threads=8 into the explicit field, not serverSettings', () => { + const a = parseArgs(['--max_insert_threads=8']) + expect(a.maxInsertThreads).toBe('8') + expect(a.serverSettings).toEqual({}) + }) + + it('exports a non-empty SERVER_SETTINGS allowlist', () => { + expect(SERVER_SETTINGS).toBeInstanceOf(Set) + expect(SERVER_SETTINGS.size).toBeGreaterThan(0) + }) + + it('silently drops unknown options', () => { + const a = parseArgs(['--brand-new-thing=xyz']) + expect(a.serverSettings).toEqual({}) + expect(a.query).toBeNull() + }) + + it('forwards --max_threads=4 as a server setting', () => { + const a = parseArgs(['--max_threads=4']) + expect(a.serverSettings).toEqual({ max_threads: '4' }) + expect(a.query).toBeNull() + }) + + it('silently drops a CLIENT_ONLY setting like --max_block_size=1024', () => { + const a = parseArgs(['--max_block_size=1024']) + expect(a.serverSettings).toEqual({}) + expect(a.query).toBeNull() + }) + + it('does not place --unknown_thing=42 into serverSettings or throw', () => { + expect(() => parseArgs(['--unknown_thing=42'])).not.toThrow() + const a = parseArgs(['--unknown_thing=42']) + expect(a.serverSettings).toEqual({}) + }) + + it('treats --max-threads=4 the same as --max_threads=4 (forwarded as server setting)', () => { + const a = parseArgs(['--max-threads=4']) + expect(a.serverSettings).toEqual({ max_threads: '4' }) + }) +}) diff --git a/tests/clickhouse-test-runner/__tests__/extract-from-config.test.ts b/tests/clickhouse-test-runner/__tests__/extract-from-config.test.ts new file mode 100644 index 000000000..be4dcb3b9 --- /dev/null +++ b/tests/clickhouse-test-runner/__tests__/extract-from-config.test.ts @@ -0,0 +1,45 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { handleExtractFromConfig } from '../src/extract-from-config.js' + +describe('handleExtractFromConfig', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + function captureStdout(): { writes: string[] } { + const writes: string[] = [] + vi.spyOn(process.stdout, 'write').mockImplementation((( + chunk: string | Uint8Array, + ): boolean => { + writes.push( + typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8'), + ) + return true + }) as typeof process.stdout.write) + return { writes } + } + + it('writes 127.0.0.1\\n for --key listen_host', () => { + const { writes } = captureStdout() + handleExtractFromConfig(['--key', 'listen_host']) + expect(writes.join('')).toBe('127.0.0.1\n') + }) + + it('writes 127.0.0.1\\n for --key=listen_host', () => { + const { writes } = captureStdout() + handleExtractFromConfig(['--key=listen_host']) + expect(writes.join('')).toBe('127.0.0.1\n') + }) + + it('writes nothing for --key foo', () => { + const { writes } = captureStdout() + handleExtractFromConfig(['--key', 'foo']) + expect(writes.join('')).toBe('') + }) + + it('writes nothing for empty args', () => { + const { writes } = captureStdout() + handleExtractFromConfig([]) + expect(writes.join('')).toBe('') + }) +}) diff --git a/tests/clickhouse-test-runner/__tests__/log.test.ts b/tests/clickhouse-test-runner/__tests__/log.test.ts new file mode 100644 index 000000000..aad77886d --- /dev/null +++ b/tests/clickhouse-test-runner/__tests__/log.test.ts @@ -0,0 +1,54 @@ +import { afterAll, beforeAll, describe, expect, it } from 'vitest' +import { + mkdtempSync, + readFileSync, + rmSync, + existsSync, + unlinkSync, +} from 'node:fs' +import { EOL } from 'node:os' +import path from 'node:path' +import { appendLog, safeForLog } from '../src/log.js' + +describe('appendLog', () => { + let tmpDir: string + + beforeAll(() => { + tmpDir = mkdtempSync(path.join(process.cwd(), '.test-tmp-cli-log-')) + }) + + afterAll(() => { + rmSync(tmpDir, { recursive: true, force: true }) + }) + + it('appends a line followed by EOL to the given file', () => { + const file = path.join(tmpDir, 'log.txt') + appendLog(file, 'hello') + expect(readFileSync(file, 'utf8')).toBe('hello' + EOL) + }) + + it('does not throw when the path is unwritable', () => { + const fallback = path.resolve(process.cwd(), 'clickhouse-client-cli.log') + const fallbackPreExisting = existsSync(fallback) + expect(() => + appendLog('/dev/null/non_writable_xyz/log', 'should-not-throw'), + ).not.toThrow() + if (!fallbackPreExisting && existsSync(fallback)) { + unlinkSync(fallback) + } + }) +}) + +describe('safeForLog', () => { + it('returns for null', () => { + expect(safeForLog(null)).toBe('') + }) + + it('returns for undefined', () => { + expect(safeForLog(undefined)).toBe('') + }) + + it('returns the original string otherwise', () => { + expect(safeForLog('x')).toBe('x') + }) +}) diff --git a/tests/clickhouse-test-runner/__tests__/split-queries.test.ts b/tests/clickhouse-test-runner/__tests__/split-queries.test.ts new file mode 100644 index 000000000..1cc72a946 --- /dev/null +++ b/tests/clickhouse-test-runner/__tests__/split-queries.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' +import { splitQueries } from '../src/split-queries.js' + +describe('splitQueries', () => { + it('splits two simple statements', () => { + expect(splitQueries('SELECT 1; SELECT 2')).toEqual(['SELECT 1', 'SELECT 2']) + }) + + it('ignores a trailing semicolon', () => { + expect(splitQueries('SELECT 1;')).toEqual(['SELECT 1']) + }) + + it('handles a single statement without trailing semicolon', () => { + expect(splitQueries('SELECT 1')).toEqual(['SELECT 1']) + }) + + it('returns [] for an empty string', () => { + expect(splitQueries('')).toEqual([]) + }) + + it('returns [] for whitespace-only input', () => { + expect(splitQueries(' \n\t ')).toEqual([]) + }) + + it('does not split on semicolons inside single quotes', () => { + expect(splitQueries("SELECT ';'; SELECT 2")).toEqual([ + "SELECT ';'", + 'SELECT 2', + ]) + }) + + it('does not split on semicolons inside double quotes', () => { + expect(splitQueries('SELECT ";"; SELECT 2')).toEqual([ + 'SELECT ";"', + 'SELECT 2', + ]) + }) + + it('does not split on semicolons inside backticks', () => { + expect(splitQueries('SELECT `a;b`; SELECT 2')).toEqual([ + 'SELECT `a;b`', + 'SELECT 2', + ]) + }) + + it('handles escaped quote inside a single-quoted string', () => { + expect(splitQueries("SELECT '\\''; SELECT 2")).toEqual([ + "SELECT '\\''", + 'SELECT 2', + ]) + }) + + it('trims whitespace around statements', () => { + expect(splitQueries(' SELECT 1 ; SELECT 2 ')).toEqual([ + 'SELECT 1', + 'SELECT 2', + ]) + }) +}) diff --git a/tests/clickhouse-test-runner/bin/clickhouse b/tests/clickhouse-test-runner/bin/clickhouse new file mode 100755 index 000000000..c29419f56 --- /dev/null +++ b/tests/clickhouse-test-runner/bin/clickhouse @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ENTRYPOINT="${SCRIPT_DIR}/../dist/main.js" + +if [[ "${1:-}" == "extract-from-config" ]]; then + shift + key="" + while [[ $# -gt 0 ]]; do + case "$1" in + --key) key="${2:-}"; shift 2 ;; + --key=*) key="${1#--key=}"; shift ;; + *) shift ;; + esac + done + if [[ "${key}" == "listen_host" ]]; then + echo "127.0.0.1" + fi + exit 0 +fi + +if [[ ! -f "${ENTRYPOINT}" ]]; then + echo "Entry point not found: ${ENTRYPOINT}" >&2 + echo "Build it first: (cd ${SCRIPT_DIR}/.. && npm install && npm run build)" >&2 + exit 1 +fi + +exec node --trace-warnings "${ENTRYPOINT}" "$@" diff --git a/tests/clickhouse-test-runner/eslint.config.mjs b/tests/clickhouse-test-runner/eslint.config.mjs new file mode 100644 index 000000000..7f83c97b0 --- /dev/null +++ b/tests/clickhouse-test-runner/eslint.config.mjs @@ -0,0 +1,18 @@ +import js from '@eslint/js' +import { defineConfig } from 'eslint/config' +import tseslint from 'typescript-eslint' +import { + typescriptEslintConfig, + testFilesOverrides, +} from '../../eslint.config.base.mjs' + +export default defineConfig( + js.configs.recommended, + ...tseslint.configs.strict, + ...tseslint.configs.stylistic, + typescriptEslintConfig(import.meta.dirname), + testFilesOverrides(), + { + ignores: ['dist', 'node_modules', 'bin', 'eslint.config.mjs'], + }, +) diff --git a/tests/clickhouse-test-runner/package-lock.json b/tests/clickhouse-test-runner/package-lock.json new file mode 100644 index 000000000..b87e792c7 --- /dev/null +++ b/tests/clickhouse-test-runner/package-lock.json @@ -0,0 +1,2818 @@ +{ + "name": "@clickhouse/clickhouse-test-runner", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@clickhouse/clickhouse-test-runner", + "dependencies": { + "@clickhouse/client": "*" + }, + "bin": { + "clickhouse-js-test-runner": "dist/main.js" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/node": "25.5.0", + "eslint": "^9.39.4", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "3.8.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.0", + "vitest": "^4.0.16" + }, + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@clickhouse/client": { + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/@clickhouse/client/-/client-1.18.4.tgz", + "integrity": "sha512-jjCrddI+e2OVXGh/MQY92K9r8Z/iwqaZtUXNI/MfZ/y9VGYwfbQsXRzp4Jv6w4Hgxvr4sLcz9YwIvkCBQ6X/mw==", + "license": "Apache-2.0", + "dependencies": { + "@clickhouse/client-common": "1.18.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@clickhouse/client-common": { + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/@clickhouse/client-common/-/client-common-1.18.4.tgz", + "integrity": "sha512-kPPtv8yQmplNAxfrAJvwBJq5dd+IWRewEbXSpUvtyEJXlrB8lt/ZH63jUS81Nmd+lK5MRvpOFXPoN3iogkvg+A==", + "license": "Apache-2.0" + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.128.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.128.0.tgz", + "integrity": "sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz", + "integrity": "sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz", + "integrity": "sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz", + "integrity": "sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz", + "integrity": "sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz", + "integrity": "sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz", + "integrity": "sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz", + "integrity": "sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz", + "integrity": "sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz", + "integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", + "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/type-utils": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", + "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", + "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.2", + "@typescript-eslint/types": "^8.59.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", + "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", + "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", + "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", + "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", + "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.2", + "@typescript-eslint/tsconfig-utils": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/visitor-keys": "8.59.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", + "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.2", + "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", + "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.18", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.18.tgz", + "integrity": "sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.128.0", + "@rolldown/pluginutils": "1.0.0-rc.18" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.18", + "@rolldown/binding-darwin-x64": "1.0.0-rc.18", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.18", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.18", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.18", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.18", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.18", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.18", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.18", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.18" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.59.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz", + "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.2", + "@typescript-eslint/parser": "8.59.2", + "@typescript-eslint/typescript-estree": "8.59.2", + "@typescript-eslint/utils": "8.59.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.11.tgz", + "integrity": "sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.14", + "rolldown": "1.0.0-rc.18", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/tests/clickhouse-test-runner/package.json b/tests/clickhouse-test-runner/package.json new file mode 100644 index 000000000..65f4a73a2 --- /dev/null +++ b/tests/clickhouse-test-runner/package.json @@ -0,0 +1,32 @@ +{ + "name": "@clickhouse/clickhouse-test-runner", + "private": true, + "description": "Node.js port of ClickHouse/clickhouse-java tests/clickhouse-client harness", + "engines": { + "node": ">=20.19.0" + }, + "type": "module", + "bin": { + "clickhouse-js-test-runner": "./dist/main.js" + }, + "scripts": { + "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/main.js", + "typecheck": "tsc --noEmit", + "lint": "eslint --max-warnings=0 .", + "lint:fix": "eslint . --fix", + "test": "vitest run --root ." + }, + "dependencies": { + "@clickhouse/client": "*" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "@types/node": "25.5.0", + "eslint": "^9.39.4", + "eslint-plugin-prettier": "^5.5.5", + "prettier": "3.8.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.0", + "vitest": "^4.0.16" + } +} diff --git a/tests/clickhouse-test-runner/scripts/run-upstream-tests.sh b/tests/clickhouse-test-runner/scripts/run-upstream-tests.sh new file mode 100755 index 000000000..7f71491e3 --- /dev/null +++ b/tests/clickhouse-test-runner/scripts/run-upstream-tests.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Resolve the runner directory (parent of scripts/) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUNNER_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Read environment variables with defaults +UPSTREAM_CLICKHOUSE_DIR="${UPSTREAM_CLICKHOUSE_DIR:-${RUNNER_DIR}/.upstream/ClickHouse}" +CLICKHOUSE_CLIENT_CLI_LOG="${CLICKHOUSE_CLIENT_CLI_LOG:-${RUNNER_DIR}/.upstream/clickhouse-client-cli.log}" +UPSTREAM_TEST_LIST="${UPSTREAM_TEST_LIST:-${RUNNER_DIR}/upstream-allowlist.txt}" + +# Build the runner if needed +if [[ ! -f "${RUNNER_DIR}/dist/main.js" ]]; then + echo "Building clickhouse-test-runner..." >&2 + (cd "$RUNNER_DIR" && npm install && npm run build) +fi + +# Verify upstream ClickHouse directory +if [[ ! -x "${UPSTREAM_CLICKHOUSE_DIR}/tests/clickhouse-test" ]]; then + echo "Error: ${UPSTREAM_CLICKHOUSE_DIR}/tests/clickhouse-test not found or not executable." >&2 + echo "Set UPSTREAM_CLICKHOUSE_DIR to point to a checkout of ClickHouse/ClickHouse." >&2 + exit 1 +fi + +# Read allowlist into array, skipping comments and blank lines. +# Leading/trailing whitespace is trimmed so test names are passed cleanly +# to tests/clickhouse-test even if the allowlist file is hand-edited. +tests=() +while IFS= read -r line || [[ -n "$line" ]]; do + # Trim leading whitespace + line="${line#"${line%%[![:space:]]*}"}" + # Trim trailing whitespace + line="${line%"${line##*[![:space:]]}"}" + # Skip blank lines and comments + [[ -z "${line}" ]] && continue + [[ "${line}" == \#* ]] && continue + tests+=("${line}") +done < "${UPSTREAM_TEST_LIST}" + +echo "Selected ${#tests[@]} test(s) from ${UPSTREAM_TEST_LIST}" >&2 + +# Optional sharding: pick a round-robin subset of the allowlist when +# SHARD_TOTAL > 1. Tests at positions where (index % SHARD_TOTAL) == +# (SHARD_INDEX - 1) are kept (1-based SHARD_INDEX). Round-robin selection +# keeps each shard a representative sample of the full allowlist regardless +# of how the allowlist is ordered, so per-shard runtimes stay roughly even. +SHARD_INDEX="${SHARD_INDEX:-1}" +SHARD_TOTAL="${SHARD_TOTAL:-1}" +if ! [[ "${SHARD_TOTAL}" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: SHARD_TOTAL must be a positive integer (got: '${SHARD_TOTAL}')." >&2 + exit 1 +fi +if ! [[ "${SHARD_INDEX}" =~ ^[1-9][0-9]*$ ]]; then + echo "Error: SHARD_INDEX must be a positive integer (got: '${SHARD_INDEX}')." >&2 + exit 1 +fi +if (( SHARD_INDEX > SHARD_TOTAL )); then + echo "Error: SHARD_INDEX (${SHARD_INDEX}) must be <= SHARD_TOTAL (${SHARD_TOTAL})." >&2 + exit 1 +fi +if (( SHARD_TOTAL > 1 )); then + sharded=() + for i in "${!tests[@]}"; do + if (( i % SHARD_TOTAL == SHARD_INDEX - 1 )); then + sharded+=("${tests[$i]}") + fi + done + echo "Sharding: keeping ${#sharded[@]} test(s) for shard ${SHARD_INDEX}/${SHARD_TOTAL}" >&2 + tests=("${sharded[@]}") +fi + +if [[ ${#tests[@]} -eq 0 ]]; then + if [[ "${ALLOW_EMPTY_UPSTREAM_ALLOWLIST:-0}" != "1" ]]; then + echo "Error: no tests were selected from ${UPSTREAM_TEST_LIST}." >&2 + echo "Refusing to run tests/clickhouse-test without explicit test names because an empty allowlist can run a large upstream suite." >&2 + echo "If this is intentional, rerun with ALLOW_EMPTY_UPSTREAM_ALLOWLIST=1." >&2 + exit 1 + fi + echo "Warning: no tests were selected from ${UPSTREAM_TEST_LIST}; continuing because ALLOW_EMPTY_UPSTREAM_ALLOWLIST=1." >&2 +fi + +# Ensure log file directory exists +mkdir -p "$(dirname "${CLICKHOUSE_CLIENT_CLI_LOG}")" + +# Export environment for the wrapper +export PATH="${RUNNER_DIR}/bin:${PATH}" +export CLICKHOUSE_CLIENT_CLI_LOG + +# Run the upstream test runner +cd "${UPSTREAM_CLICKHOUSE_DIR}" +exec python3 tests/clickhouse-test "${tests[@]}" "$@" diff --git a/tests/clickhouse-test-runner/src/args.ts b/tests/clickhouse-test-runner/src/args.ts new file mode 100644 index 000000000..3d8e77287 --- /dev/null +++ b/tests/clickhouse-test-runner/src/args.ts @@ -0,0 +1,256 @@ +import { classifySetting } from './settings.js' + +export interface ParsedArgs { + host: string + port: number + user: string + password: string + database: string + secure: boolean + query: string | null + logComment: string | null + sendLogsLevel: string | null + maxInsertThreads: string | null + multiquery: boolean + help: boolean + serverSettings: Record + rawArgv: string[] +} + +interface OptionSpec { + long: string + short?: string + hasArg: boolean +} + +const BASE_OPTIONS: readonly OptionSpec[] = [ + { long: 'host', short: 'h', hasArg: true }, + { long: 'port', hasArg: true }, + { long: 'user', short: 'u', hasArg: true }, + { long: 'password', hasArg: true }, + { long: 'database', short: 'd', hasArg: true }, + { long: 'query', short: 'q', hasArg: true }, + { long: 'log_comment', hasArg: true }, + { long: 'log-comment', hasArg: true }, + { long: 'send_logs_level', hasArg: true }, + { long: 'send-logs-level', hasArg: true }, + { long: 'max_insert_threads', hasArg: true }, + { long: 'max-insert-threads', hasArg: true }, + { long: 'secure', short: 's', hasArg: false }, + { long: 'multiline', short: 'n', hasArg: false }, + { long: 'multiquery', hasArg: false }, + { long: 'multi-query', hasArg: false }, + { long: 'help', hasArg: false }, +] + +export const KNOWN_LONG_OPTIONS: ReadonlySet = new Set( + BASE_OPTIONS.map((o) => o.long), +) + +const SHORT_TO_LONG: ReadonlyMap = new Map( + BASE_OPTIONS.filter((o) => o.short !== undefined).map((o) => [ + o.short as string, + o.long, + ]), +) + +const LONG_HAS_ARG: ReadonlyMap = new Map( + BASE_OPTIONS.map((o) => [o.long, o.hasArg]), +) + +const USAGE_TEXT = [ + 'usage: clickhouse-client [options]', + '', + 'Known server settings are forwarded to ClickHouse.', + 'Client-only and unknown settings are accepted but not sent to server.', + 'If --query is not specified, the query is read from stdin.', + '', + 'Options:', + ' -h, --host HOST Server host (default: localhost)', + ' --port PORT HTTP port (default: 8123)', + ' -u, --user USER Username (default: default)', + ' --password PASSWORD Password (default: empty)', + ' -d, --database DB Database (default: default)', + ' -q, --query SQL SQL query to execute', + ' --log_comment VALUE Comment for query_log records', + ' --send_logs_level VALUE Server log level to send with result', + ' --max_insert_threads VALUE', + ' Max insert threads setting', + ' -s, --secure Use HTTPS', + ' -n, --multiline (ignored, accepted for compatibility)', + ' --multiquery Execute multiple ";"-separated queries', + ' --help Print this help', + '', + 'Environment variables:', + ' CLICKHOUSE_CLIENT_CLI_LOG Path to log file for troubleshooting', + '', +].join('\n') + +export function printUsage( + stream: NodeJS.WritableStream = process.stdout, +): void { + stream.write(USAGE_TEXT) +} + +export function parseArgs(argv: string[]): ParsedArgs { + const parsed: ParsedArgs = { + host: 'localhost', + port: 8123, + user: 'default', + password: '', + database: 'default', + secure: false, + query: null, + logComment: null, + sendLogsLevel: null, + maxInsertThreads: null, + multiquery: false, + help: false, + serverSettings: {}, + rawArgv: [...argv], + } + + // Map of canonical long option -> value (string) or true for flags. + const seen = new Map() + + let i = 0 + while (i < argv.length) { + const arg = argv[i] + if (arg === undefined) { + i++ + continue + } + if (arg === '--') { + break + } + + if (arg.startsWith('--')) { + const eq = arg.indexOf('=') + const name = eq >= 0 ? arg.substring(2, eq) : arg.substring(2) + const inlineValue = eq >= 0 ? arg.substring(eq + 1) : undefined + + if (name.length === 0) { + i++ + continue + } + + if (LONG_HAS_ARG.has(name)) { + const hasArg = LONG_HAS_ARG.get(name) === true + if (!hasArg) { + seen.set(name, true) + i++ + continue + } + let value: string + if (inlineValue !== undefined) { + value = inlineValue + } else { + const next = argv[i + 1] + if (next === undefined) { + // Missing required arg: skip silently to mirror lenient behavior. + i++ + continue + } + value = next + i++ + } + seen.set(name, value) + i++ + continue + } + + // Dynamic / unknown long option. Optional arg. + let value: string | undefined = inlineValue + if (value === undefined) { + const next = argv[i + 1] + if (next !== undefined) { + const isNegativeNumber = + /^-(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(next) + const isKnownLongOption = (() => { + if (!next.startsWith('--')) return false + const nextEq = next.indexOf('=') + const nextName = + nextEq >= 0 ? next.substring(2, nextEq) : next.substring(2) + return nextName.length > 0 && LONG_HAS_ARG.has(nextName) + })() + + if (!next.startsWith('-') || isNegativeNumber || !isKnownLongOption) { + value = next + i++ + } + } + } + const settingName = name.split('-').join('_') + if (classifySetting(settingName) === 'server') { + parsed.serverSettings[settingName] = value ?? '1' + } + // CLIENT_ONLY / UNKNOWN: silently dropped. + i++ + continue + } + + if (arg.startsWith('-') && arg.length > 1) { + // Short option (single-char). We do not bundle. + const shortName = arg.substring(1) + const longName = SHORT_TO_LONG.get(shortName) + if (longName === undefined) { + i++ + continue + } + const hasArg = LONG_HAS_ARG.get(longName) === true + if (!hasArg) { + seen.set(longName, true) + i++ + continue + } + const next = argv[i + 1] + if (next === undefined) { + i++ + continue + } + seen.set(longName, next) + i += 2 + continue + } + + // Positional argument: ignored. + i++ + } + + const firstNonNull = (...names: string[]): string | null => { + for (const n of names) { + const v = seen.get(n) + if (typeof v === 'string') { + return v + } + } + return null + } + + const hostVal = firstNonNull('host') + if (hostVal !== null) parsed.host = hostVal + const portVal = firstNonNull('port') + if (portVal !== null) { + const n = Number.parseInt(portVal, 10) + if (!Number.isNaN(n)) parsed.port = n + } + const userVal = firstNonNull('user') + if (userVal !== null) parsed.user = userVal + const passwordVal = firstNonNull('password') + if (passwordVal !== null) parsed.password = passwordVal + const databaseVal = firstNonNull('database') + if (databaseVal !== null) parsed.database = databaseVal + parsed.query = firstNonNull('query') + parsed.logComment = firstNonNull('log_comment', 'log-comment') + parsed.sendLogsLevel = firstNonNull('send_logs_level', 'send-logs-level') + parsed.maxInsertThreads = firstNonNull( + 'max_insert_threads', + 'max-insert-threads', + ) + parsed.secure = seen.get('secure') === true + parsed.multiquery = + seen.get('multiquery') === true || seen.get('multi-query') === true + parsed.help = seen.get('help') === true + + return parsed +} diff --git a/tests/clickhouse-test-runner/src/backends/client.ts b/tests/clickhouse-test-runner/src/backends/client.ts new file mode 100644 index 000000000..82749e4a6 --- /dev/null +++ b/tests/clickhouse-test-runner/src/backends/client.ts @@ -0,0 +1,62 @@ +import { createClient } from '@clickhouse/client' +import type { ParsedArgs } from '../args.js' +import { appendLog } from '../log.js' + +export interface BackendOptions { + args: ParsedArgs + queries: string[] + logPath: string +} + +function buildClickHouseSettings( + args: ParsedArgs, +): Record { + const settings: Record = {} + settings['default_format'] = 'TabSeparated' + if (args.logComment !== null && args.logComment.length > 0) { + settings['log_comment'] = args.logComment + } + if (args.sendLogsLevel !== null && args.sendLogsLevel.length > 0) { + settings['send_logs_level'] = args.sendLogsLevel + } + if (args.maxInsertThreads !== null && args.maxInsertThreads.length > 0) { + settings['max_insert_threads'] = args.maxInsertThreads + } + for (const [k, v] of Object.entries(args.serverSettings)) { + settings[k] = v + } + return settings +} + +export async function executeWithClient(opts: BackendOptions): Promise { + const { args, queries, logPath } = opts + const proto = args.secure ? 'https' : 'http' + const url = `${proto}://${args.host}:${args.port}` + const client = createClient({ + url, + username: args.user, + password: args.password, + database: args.database, + }) + + const clickhouse_settings = buildClickHouseSettings(args) + + try { + for (const q of queries) { + appendLog(logPath, 'executing_query=' + q) + const result = await client.exec({ + query: q, + clickhouse_settings, + }) + for await (const chunk of result.stream) { + process.stdout.write(chunk) + } + } + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + appendLog(logPath, 'error=' + msg) + throw err + } finally { + await client.close() + } +} diff --git a/tests/clickhouse-test-runner/src/extract-from-config.ts b/tests/clickhouse-test-runner/src/extract-from-config.ts new file mode 100644 index 000000000..4a1d65ce4 --- /dev/null +++ b/tests/clickhouse-test-runner/src/extract-from-config.ts @@ -0,0 +1,27 @@ +export function handleExtractFromConfig(args: string[]): void { + let key: string | null = null + + for (let i = 0; i < args.length; i++) { + const arg = args[i] + if (arg === undefined) continue + + if (arg.startsWith('--key=')) { + key = arg.substring('--key='.length) + continue + } + if (arg === '--key') { + const next = args[i + 1] + if (next !== undefined) { + key = next + i++ + } + continue + } + } + + if (key === 'listen_host') { + process.stdout.write('127.0.0.1\n') + } + + process.exitCode = 0 +} diff --git a/tests/clickhouse-test-runner/src/log.ts b/tests/clickhouse-test-runner/src/log.ts new file mode 100644 index 000000000..47fb8700c --- /dev/null +++ b/tests/clickhouse-test-runner/src/log.ts @@ -0,0 +1,42 @@ +import { appendFileSync, mkdirSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { EOL } from 'node:os' + +const DEFAULT_LOG_PATH = '/tmp/clickhouse-client-cli.log' +const FALLBACK_LOG_FILENAME = 'clickhouse-client-cli.log' + +export function resolveLogPath(): string { + const fromEnv = process.env.CLICKHOUSE_CLIENT_CLI_LOG + if (fromEnv !== undefined && fromEnv.trim().length > 0) { + return fromEnv + } + return DEFAULT_LOG_PATH +} + +function tryAppend(path: string, payload: string): boolean { + try { + const parent = dirname(path) + if (parent.length > 0) { + mkdirSync(parent, { recursive: true }) + } + appendFileSync(path, payload, 'utf8') + return true + } catch { + return false + } +} + +export function appendLog(path: string, line: string): void { + const payload = line + EOL + if (tryAppend(path, payload)) { + return + } + const fallback = resolve(process.cwd(), FALLBACK_LOG_FILENAME) + if (fallback !== path) { + tryAppend(fallback, payload) + } +} + +export function safeForLog(value: string | null | undefined): string { + return value === null || value === undefined ? '' : value +} diff --git a/tests/clickhouse-test-runner/src/main.ts b/tests/clickhouse-test-runner/src/main.ts new file mode 100644 index 000000000..a204043ac --- /dev/null +++ b/tests/clickhouse-test-runner/src/main.ts @@ -0,0 +1,87 @@ +#!/usr/bin/env node +import { readFileSync } from 'node:fs' +import { parseArgs, printUsage } from './args.js' +import { appendLog, resolveLogPath, safeForLog } from './log.js' +import { splitQueries } from './split-queries.js' +import { handleExtractFromConfig } from './extract-from-config.js' +import { executeWithClient } from './backends/client.js' + +async function main(): Promise { + const argv = process.argv.slice(2) + + if (argv[0] === 'extract-from-config') { + handleExtractFromConfig(argv.slice(1)) + return + } + + const logPath = resolveLogPath() + appendLog(logPath, '=== clickhouse-client invocation ===') + appendLog(logPath, 'timestamp=' + new Date().toISOString()) + appendLog(logPath, 'argv=' + JSON.stringify(argv)) + + let args + try { + args = parseArgs(argv) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + process.stderr.write('Error: ' + msg + '\n') + printUsage(process.stderr) + process.exitCode = 1 + return + } + + if (args.help) { + printUsage(process.stdout) + return + } + + let query: string | null = args.query + if (query === null) { + try { + query = readFileSync(0, 'utf8') + } catch { + query = null + } + } + + if (query === null || query.trim().length === 0) { + process.stderr.write( + 'No query provided. Use --query or pipe SQL via stdin.\n', + ) + process.exitCode = 1 + return + } + + const queries = args.multiquery ? splitQueries(query) : [query.trim()] + if (queries.length === 0) { + process.stderr.write( + 'No query provided. Use --query or pipe SQL via stdin.\n', + ) + process.exitCode = 1 + return + } + + appendLog(logPath, 'database=' + args.database) + appendLog(logPath, 'user=' + args.user) + appendLog(logPath, 'secure=' + String(args.secure)) + appendLog(logPath, 'multiquery=' + String(args.multiquery)) + appendLog(logPath, 'log_comment=' + safeForLog(args.logComment)) + appendLog(logPath, 'send_logs_level=' + safeForLog(args.sendLogsLevel)) + appendLog(logPath, 'max_insert_threads=' + safeForLog(args.maxInsertThreads)) + appendLog(logPath, 'server_settings=' + JSON.stringify(args.serverSettings)) + appendLog(logPath, 'queries_count=' + String(queries.length)) + for (const q of queries) { + appendLog(logPath, 'query=' + q) + } + + try { + await executeWithClient({ args, queries, logPath }) + } catch (err) { + const msg = err instanceof Error ? err.message : String(err) + appendLog(logPath, 'error=' + msg) + process.stderr.write('Error: ' + msg + '\n') + process.exitCode = 1 + } +} + +void main() diff --git a/tests/clickhouse-test-runner/src/settings.ts b/tests/clickhouse-test-runner/src/settings.ts new file mode 100644 index 000000000..b00ade99c --- /dev/null +++ b/tests/clickhouse-test-runner/src/settings.ts @@ -0,0 +1,162 @@ +export type SettingScope = 'server' | 'client_only' | 'unknown' + +export const SERVER_SETTINGS: ReadonlySet = new Set([ + 'max_insert_threads', + 'max_threads', + 'send_logs_level', +]) + +export const CLIENT_ONLY_SETTINGS: ReadonlySet = new Set([ + 'group_by_two_level_threshold', + 'group_by_two_level_threshold_bytes', + 'distributed_aggregation_memory_efficient', + 'fsync_metadata', + 'output_format_parallel_formatting', + 'input_format_parallel_parsing', + 'min_chunk_bytes_for_parallel_parsing', + 'max_read_buffer_size', + 'prefer_localhost_replica', + 'max_block_size', + 'max_joined_block_size_rows', + 'joined_block_split_single_row', + 'join_output_by_rowlist_perkey_rows_threshold', + 'optimize_append_index', + 'use_hedged_requests', + 'optimize_if_chain_to_multiif', + 'optimize_if_transform_strings_to_enum', + 'optimize_read_in_order', + 'optimize_or_like_chain', + 'optimize_substitute_columns', + 'enable_multiple_prewhere_read_steps', + 'read_in_order_two_level_merge_threshold', + 'optimize_aggregation_in_order', + 'aggregation_in_order_max_block_bytes', + 'use_uncompressed_cache', + 'min_bytes_to_use_direct_io', + 'min_bytes_to_use_mmap_io', + 'local_filesystem_read_method', + 'remote_filesystem_read_method', + 'local_filesystem_read_prefetch', + 'filesystem_cache_segments_batch_size', + 'read_from_filesystem_cache_if_exists_otherwise_bypass_cache', + 'throw_on_error_from_cache_on_write_operations', + 'remote_filesystem_read_prefetch', + 'distributed_cache_discard_connection_if_unread_data', + 'distributed_cache_use_clients_cache_for_write', + 'distributed_cache_use_clients_cache_for_read', + 'allow_prefetched_read_pool_for_remote_filesystem', + 'filesystem_prefetch_max_memory_usage', + 'filesystem_prefetches_limit', + 'filesystem_prefetch_min_bytes_for_single_read_task', + 'filesystem_prefetch_step_marks', + 'filesystem_prefetch_step_bytes', + 'enable_filesystem_cache', + 'enable_filesystem_cache_on_write_operations', + 'compile_expressions', + 'compile_aggregate_expressions', + 'compile_sort_description', + 'merge_tree_coarse_index_granularity', + 'optimize_distinct_in_order', + 'max_bytes_before_remerge_sort', + 'min_compress_block_size', + 'max_compress_block_size', + 'merge_tree_compact_parts_min_granules_to_multibuffer_read', + 'optimize_sorting_by_input_stream_properties', + 'http_response_buffer_size', + 'http_wait_end_of_query', + 'enable_memory_bound_merging_of_aggregation_results', + 'min_count_to_compile_expression', + 'min_count_to_compile_aggregate_expression', + 'min_count_to_compile_sort_description', + 'session_timezone', + 'use_page_cache_for_disks_without_file_cache', + 'use_page_cache_for_local_disks', + 'use_page_cache_for_object_storage', + 'page_cache_inject_eviction', + 'merge_tree_read_split_ranges_into_intersecting_and_non_intersecting_injection_probability', + 'prefer_external_sort_block_bytes', + 'cross_join_min_rows_to_compress', + 'cross_join_min_bytes_to_compress', + 'min_external_table_block_size_bytes', + 'max_parsing_threads', + 'optimize_functions_to_subcolumns', + 'parallel_replicas_local_plan', + 'query_plan_join_swap_table', + 'enable_vertical_final', + 'optimize_extract_common_expressions', + 'optimize_syntax_fuse_functions', + 'use_async_executor_for_materialized_views', + 'use_query_condition_cache', + 'secondary_indices_enable_bulk_filtering', + 'use_skip_indexes_if_final', + 'use_skip_indexes_on_data_read', + 'optimize_rewrite_like_perfect_affix', + 'input_format_parquet_use_native_reader_v3', + 'enable_lazy_columns_replication', + 'allow_special_serialization_kinds_in_output_formats', + 'short_circuit_function_evaluation_for_nulls_threshold', + 'automatic_parallel_replicas_mode', + 'temporary_files_buffer_size', + 'query_plan_optimize_join_order_algorithm', + 'max_bytes_before_external_sort', + 'max_bytes_before_external_group_by', + 'max_bytes_ratio_before_external_sort', + 'max_bytes_ratio_before_external_group_by', + 'allow_repeated_settings', + 'use_skip_indexes_if_final_exact_mode', + 'ratio_of_defaults_for_sparse_serialization', + 'prefer_fetch_merged_part_size_threshold', + 'vertical_merge_algorithm_min_rows_to_activate', + 'vertical_merge_algorithm_min_columns_to_activate', + 'allow_vertical_merges_from_compact_to_wide_parts', + 'min_merge_bytes_to_use_direct_io', + 'index_granularity_bytes', + 'merge_max_block_size', + 'index_granularity', + 'min_bytes_for_wide_part', + 'compress_marks', + 'compress_primary_key', + 'marks_compress_block_size', + 'primary_key_compress_block_size', + 'replace_long_file_name_to_hash', + 'max_file_name_length', + 'min_bytes_for_full_part_storage', + 'compact_parts_max_bytes_to_buffer', + 'compact_parts_max_granules_to_buffer', + 'compact_parts_merge_max_bytes_to_prefetch_part', + 'cache_populated_by_fetch', + 'concurrent_part_removal_threshold', + 'old_parts_lifetime', + 'prewarm_mark_cache', + 'use_const_adaptive_granularity', + 'enable_index_granularity_compression', + 'enable_block_number_column', + 'enable_block_offset_column', + 'use_primary_key_cache', + 'prewarm_primary_key_cache', + 'object_serialization_version', + 'object_shared_data_serialization_version', + 'object_shared_data_serialization_version_for_zero_level_parts', + 'object_shared_data_buckets_for_compact_part', + 'object_shared_data_buckets_for_wide_part', + 'dynamic_serialization_version', + 'auto_statistics_types', + 'serialization_info_version', + 'string_serialization_version', + 'nullable_serialization_version', + 'enable_shared_storage_snapshot_in_query', + 'min_columns_to_activate_adaptive_write_buffer', + 'reduce_blocking_parts_sleep_ms', + 'shared_merge_tree_outdated_parts_group_size', + 'shared_merge_tree_max_outdated_parts_to_process_at_once', +]) + +export function classifySetting(name: string): SettingScope { + if (SERVER_SETTINGS.has(name)) { + return 'server' + } + if (CLIENT_ONLY_SETTINGS.has(name)) { + return 'client_only' + } + return 'unknown' +} diff --git a/tests/clickhouse-test-runner/src/split-queries.ts b/tests/clickhouse-test-runner/src/split-queries.ts new file mode 100644 index 000000000..afe09b1f8 --- /dev/null +++ b/tests/clickhouse-test-runner/src/split-queries.ts @@ -0,0 +1,59 @@ +export function splitQueries(sql: string): string[] { + const queries: string[] = [] + let current = '' + + let inSingleQuote = false + let inDoubleQuote = false + let inBacktick = false + let escaping = false + + for (let i = 0; i < sql.length; i++) { + const ch = sql.charAt(i) + + if (escaping) { + current += ch + escaping = false + continue + } + + if ((inSingleQuote || inDoubleQuote) && ch === '\\') { + current += ch + escaping = true + continue + } + + if (!inDoubleQuote && !inBacktick && ch === "'") { + inSingleQuote = !inSingleQuote + current += ch + continue + } + if (!inSingleQuote && !inBacktick && ch === '"') { + inDoubleQuote = !inDoubleQuote + current += ch + continue + } + if (!inSingleQuote && !inDoubleQuote && ch === '`') { + inBacktick = !inBacktick + current += ch + continue + } + + if (!inSingleQuote && !inDoubleQuote && !inBacktick && ch === ';') { + const statement = current.trim() + if (statement.length > 0) { + queries.push(statement) + } + current = '' + continue + } + + current += ch + } + + const trailing = current.trim() + if (trailing.length > 0) { + queries.push(trailing) + } + + return queries +} diff --git a/tests/clickhouse-test-runner/tsconfig.build.json b/tests/clickhouse-test-runner/tsconfig.build.json new file mode 100644 index 000000000..7bbac0b24 --- /dev/null +++ b/tests/clickhouse-test-runner/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"] +} diff --git a/tests/clickhouse-test-runner/tsconfig.json b/tests/clickhouse-test-runner/tsconfig.json new file mode 100644 index 000000000..946ab4c87 --- /dev/null +++ b/tests/clickhouse-test-runner/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "types": ["node"] + }, + "include": ["src/**/*.ts", "__tests__/**/*.ts", "vitest.config.ts"] +} diff --git a/tests/clickhouse-test-runner/upstream-allowlist.txt b/tests/clickhouse-test-runner/upstream-allowlist.txt new file mode 100644 index 000000000..3b8342b2b --- /dev/null +++ b/tests/clickhouse-test-runner/upstream-allowlist.txt @@ -0,0 +1,3172 @@ +# Upstream ClickHouse SQL tests known/expected to pass through @clickhouse/client +# via the tests/clickhouse-test-runner harness. +# +# Conventions: +# - One test name per line (matches the pattern argument of tests/clickhouse-test). +# - Lines starting with '#' and blank lines are ignored. +# - Add tests here once they reliably pass through the harness. +# - Remove (or comment out with a reason) tests that begin to flake. +# - Avoid bare prefixes that overlap with failing tests: `tests/clickhouse-test` +# treats positional arguments as substring matches, so e.g. `00396_uuid` would +# also pull in `00396_uuid_v7`. +# +# To extend this list, see tests/clickhouse-test-runner/README.md. + + +00001_select_1 +00003_reinterpret_as_string +00007_array +00008_array_join +00009_array_join_subquery +00012_array_join_alias_2 +00013_create_table_with_arrays +00014_select_from_table_with_nested +00015_totals_having_constants +00016_totals_having_constants +00018_distinct_in_subquery +00022_func_higher_order_and_constants +00023_agg_select_agg_subquery +00025_implicitly_used_subquery_column +00027_argMinMax +00031_parser_number +00032_fixed_string_to_string +00033_fixed_string_to_string +00034_fixed_string_to_number +00035_function_array_return_type +00036_array_element +00040_array_enumerate_uniq +00041_aggregation_remap +00041_big_array_join +00042_set +00044_sorting_by_string_descending +00045_sorting_by_fixed_string_descending +00049_any_left_join +00050_any_left_join +00051_any_inner_join +00052_all_left_join +00053_all_inner_join +00054_join_string +00055_join_two_numbers +00056_join_number_string +00067_replicate_segfault +00068_empty_tiny_log +00069_date_arithmetic +00077_set_keys_fit_128_bits_many_blocks +00078_string_concat +00087_distinct_of_empty_arrays +00087_math_functions +00088_distinct_of_arrays_of_strings +00089_group_by_arrays_of_fixed +00098_1_union_all +00098_2_union_all +00098_3_union_all +00098_4_union_all +00098_5_union_all +00098_6_union_all +00098_7_union_all +00098_8_union_all +00098_9_union_all +00098_a_union_all +00098_b_union_all +00098_c_union_all +00098_d_union_all +00098_e_union_all +00098_f_union_all +00098_g_union_all +00098_h_union_all +00098_j_union_all +00098_l_union_all +00099_join_many_blocks_segfault +00103_ipv4_num_to_string_class_c +00114_float_type_result_of_division +00116_storage_set +00117_parsing_arrays +00119_storage_join +00120_join_and_group_by +00122_join_with_subquery_with_subquery +00125_array_element_of_array_of_tuple +00127_group_by_concat +00128_group_by_number_and_fixed_string +00129_quantile_timing_weighted +00131_set_hashed +00132_sets +00134_aggregation_by_fixed_string_of_size_1_2_4_8 +00136_duplicate_order_by_elems +00137_in_constants +00138_table_aliases +00140_parse_unix_timestamp_as_datetime +00142_parse_timestamp_as_datetime +00143_number_classification_functions +00144_empty_regexp +00145_empty_likes +00149_function_url_hash +00151_tuple_with_array +00152_totals_in_subquery +00156_array_map_to_constant +00157_aliases_and_lambda_formal_parameters +00159_whitespace_in_columns_list +00160_merge_and_index_in_in +00164_not_chain +00165_transform_non_const_default +00167_settings_inside_query +00169_join_constant_keys +00170_lower_upper_utf8 +00170_lower_upper_utf8_memleak +00172_constexprs_in_set +00173_compare_date_time_with_constant_string +00174_compare_date_time_with_constant_string_in_in +00175_if_num_arrays +00175_partition_by_ignore +00176_if_string_arrays +00178_function_replicate +00178_query_datetime64_index +00179_lambdas_with_common_expressions_and_filter +00180_attach_materialized_view +00185_array_literals +00187_like_regexp_prefix +00188_constants_as_arguments_of_aggregate_functions +00190_non_constant_array_of_constant_data +00192_least_greatest +00194_identity +00196_float32_formatting +00197_if_fixed_string +00198_group_by_empty_arrays +00201_array_uniq +00202_cross_join +00204_extract_url_parameter +00205_emptyscalar_subquery_type_mismatch_bug +00206_empty_array_to_single +00207_left_array_join +00208_agg_state_merge +00209_insert_select_extremes +00216_bit_test_function_family +00218_like_regexp_newline +00219_full_right_join_column_order +00222_sequence_aggregate_function_family +00227_quantiles_timing_arbitrary_order +00230_array_functions_has_count_equal_index_of_non_const_second_arg +00231_format_vertical_raw +00232_format_readable_decimal_size +00232_format_readable_size +00233_position_function_family +00233_position_function_sql_comparibilty +00234_disjunctive_equality_chains_optimization +00237_group_by_arrays +00238_removal_of_temporary_columns +00239_type_conversion_in_in +00240_replace_substring_loop +00250_tuple_comparison +00251_has_types +00254_tuple_extremes +00255_array_concat_string +00256_reverse +00258_materializing_tuples +00259_hashing_tuples +00260_like_and_curly_braces +00263_merge_aggregates_and_overflow +00264_uniq_many_args +00266_read_overflow_mode +00267_tuple_array_access_operators_priority +00268_aliases_without_as_keyword +00269_database_table_whitespace +00270_views_query_processing_stage +00271_agg_state_and_totals +00272_union_all_and_in_subquery +00277_array_filter +00280_hex_escape_sequence +00283_column_cut +00287_column_const_with_nan +00288_empty_stripelog +00291_array_reduce +00292_parser_tuple_element +00296_url_parameters +00299_stripe_log_multiple_inserts +00300_csv +00306_insert_values_and_expressions +00312_position_case_insensitive_utf8 +00315_quantile_off_by_one +00316_rounding_functions_and_empty_block +00317_in_tuples_and_out_of_range_values +00320_between +00323_quantiles_timing_bug +00324_hashing_enums +00330_view_subqueries +00331_final_and_prewhere_condition_ver_column +00332_quantile_timing_memory_leak +00333_parser_number_bug +00334_column_aggregate_function_limit +00338_replicate_array_of_strings +00342_escape_sequences +00343_array_element_generic +00346_if_tuple +00347_has_tuple +00348_tuples +00349_visible_width +00350_count_distinct +00351_select_distinct_arrays_tuples +00353_join_by_tuple +00355_array_of_non_const_convertible_types +00356_analyze_aggregations_and_union_all +00357_to_string_complex_types +00358_from_string_complex_types +00359_convert_or_zero_functions +00360_to_date_from_string_with_datetime +00362_great_circle_distance +00364_java_style_denormals +00367_visible_width_of_array_tuple_enum +00369_int_div_of_float +00371_union_all +00373_group_by_tuple +00374_any_last_if_merge +00381_first_significant_subdomain +00388_enum_with_totals +00389_concat_operator +00393_if_with_constant_condition +00397_tsv_format_synonym +00399_group_uniq_array_date_datetime +00401_merge_and_stripelog +00402_nan_and_extremes +00403_to_start_of_day +00404_null_literal +00406_tuples_with_nulls +00413_least_greatest_new_behavior +00414_time_zones_direct_conversion +00420_null_in_scalar_subqueries +00422_hash_function_constexpr +00423_storage_log_single_thread +00425_count_nullable +00426_nulls_sorting +00429_point_in_ellipses +00431_if_nulls +00433_ifnull +00434_tonullable +00435_coalesce +00436_convert_charset +00436_fixed_string_16_comparisons +00437_nulls_first_last +00438_bit_rotate +00439_fixed_string_filter +00441_nulls_in +00442_filter_by_nullable +00445_join_nullable_keys +00447_foreach_modifier +00448_replicate_nullable_tuple_generic +00448_to_string_cut_to_zero +00449_filter_array_nullable_tuple +00450_higher_order_and_nullable +00451_left_array_join_and_constants +00452_left_array_join_and_nullable +00453_top_k +00457_log_tinylog_stripelog_nullable +00459_group_array_insert_at +00461_default_value_of_argument_type +00462_json_true_false_literals +00464_array_element_out_of_range +00464_sort_all_constant_columns +00465_nullable_default +00466_comments_in_keyword +00468_array_join_multiple_arrays_and_use_original_column +00469_comparison_of_strings_containing_null_char +00470_identifiers_in_double_quotes +00471_sql_style_quoting +00472_compare_uuid_with_constant_string +00472_create_view_if_not_exists +00475_in_join_db_table +00477_parsing_data_types +00479_date_and_datetime_to_number +00480_mac_addresses +00481_create_view_for_null +00482_subqueries_and_aliases +00483_cast_syntax +00486_if_fixed_string +00487_if_array_fixed_string +00488_column_name_primary +00488_non_ascii_column_names +00490_special_line_separators_and_characters_outside_of_bmp +00490_with_select +00498_array_functions_concat_slice_push_pop +00498_bitwise_aggregate_functions +00499_json_enum_insert +00500_point_in_polygon_2d_const +00500_point_in_polygon_3d_const +00500_point_in_polygon_bug_2 +00500_point_in_polygon_nan +00500_point_in_polygon_non_const_poly +00502_custom_partitioning_local +00502_string_concat_with_array +00503_cast_const_nullable +00507_sumwithoverflow +00511_get_size_of_enum +00513_fractional_time_zones +00516_is_inf_nan +00516_modulo +00517_date_parsing +00518_extract_all_and_empty_matches +00520_tuple_values_interpreter +00521_multidimensional +00522_multidimensional +00523_aggregate_functions_in_group_array +00524_time_intervals_months_underflow +00525_aggregate_functions_of_nullable_that_return_non_nullable +00526_array_join_with_arrays_of_nullable +00527_totals_having_nullable +00528_const_of_nullable +00529_orantius +00530_arrays_of_nothing +00532_topk_generic +00533_uniq_array +00534_exp10 +00535_parse_float_scientific +00537_quarters +00538_datediff +00538_datediff_plural_units +00539_functions_for_working_with_json +00541_kahan_sum +00541_to_start_of_fifteen_minutes +00544_agg_foreach_of_two_arg +00544_insert_with_select +00545_weird_aggregate_functions +00547_named_tuples +00548_slice_of_nested +00551_parse_or_null +00552_logical_functions_simple +00552_logical_functions_ternary +00552_logical_functions_uint8_as_bool +00552_or_nullable +00553_buff_exists_materlized_column +00553_invalid_nested_name +00554_nested_and_table_engines +00555_right_join_excessive_rows +00556_array_intersect +00556_remove_columns_from_subquery +00557_alter_null_storage_tables +00558_parse_floats +00559_filter_array_generic +00562_in_subquery_merge_tree +00562_rewrite_select_expression_with_union +00566_enum_min_max +00568_empty_function_with_fixed_string +00570_empty_array_is_const +00571_alter_nullable +00576_nested_and_prewhere +00578_merge_table_and_table_virtual_column +00578_merge_trees_without_primary_key +00579_merge_tree_partition_and_primary_keys_using_same_expression +00580_cast_nullable_to_non_nullable +00582_not_aliasing_functions +00583_limit_by_expressions +00585_union_all_subquery_aggregation_column_removal +00587_union_all_type_conversions +00589_removal_unused_columns_aggregation +00590_limit_by_column_removal +00591_columns_removal_union_all +00592_union_all_different_aliases +00593_union_all_assert_columns_removed +00597_with_totals_on_empty_set +00599_create_view_with_subquery +00603_system_parts_nonexistent_database +00605_intersections_aggregate_functions +00606_quantiles_and_nans +00607_index_in_in +00608_uniq_array +00609_prewhere_and_default +00612_count +00612_union_query_with_subquery +00617_array_in +00618_nullable_in +00619_union_highlite +00622_select_in_parens +00624_length_utf8 +00625_arrays_in_nested +00626_in_syntax +00627_recursive_alias +00628_in_lambda_on_merge_table_bug +00633_func_or_in +00634_rename_view +00639_startsWith +00642_cast +00644_different_expressions_with_same_alias +00647_histogram +00647_histogram_negative +00647_select_numbers_with_offset +00649_quantile_tdigest_negative +00650_array_enumerate_uniq_with_tuples +00653_monotonic_integer_cast +00661_array_has_silviucpp +00662_array_has_nullable +00662_has_nullable +00663_tiny_log_empty_insert +00664_cast_from_string_to_nullable +00665_alter_nullable_string_to_nullable_uint8 +00666_uniq_complex_types +00667_compare_arrays_of_different_types +00668_compare_arrays_silviucpp +00671_max_intersections +00672_arrayDistinct +00673_subquery_prepared_set_performance +00674_has_array_enum +00676_group_by_in +00678_murmurhash +00679_uuid_in_key +00680_duplicate_columns_inside_union_all +00681_duplicate_columns_inside_union_all_stas_sviridov +00687_insert_into_mv +00688_aggregation_retention +00688_case_without_else +00688_low_cardinality_alter_add_column +00688_low_cardinality_defaults +00688_low_cardinality_dictionary_deserialization +00688_low_cardinality_prewhere +00688_low_cardinality_serialization +00689_join_table_function +00691_array_distinct +00696_system_columns_limit +00700_decimal_array_functions +00700_decimal_defaults +00700_decimal_gathers +00700_decimal_in_keys +00700_decimal_math +00700_decimal_null +00700_decimal_round +00700_decimal_with_default_precision_and_scale +00701_context_use_after_free +00702_join_with_using_dups +00702_where_with_quailified_names +00703_join_crash +00704_arrayCumSumLimited_arrayDifference +00710_array_enumerate_dense +00711_array_enumerate_variants +00712_prewhere_with_alias_and_virtual_column +00712_prewhere_with_missing_columns +00712_prewhere_with_missing_columns_2 +00713_collapsing_merge_tree +00715_bounding_ratio +00715_bounding_ratio_merge_empty +00717_default_join_type +00717_low_cardinaliry_group_by +00718_format_datetime_1 +00719_format_datetime_f_varsize_bug +00719_format_datetime_rand +00720_combinations_of_aggregate_combinators +00720_with_cube +00722_inner_join +00723_remerge_sort +00725_join_on_bug_1 +00725_join_on_bug_3 +00725_join_on_bug_4 +00726_length_aliases +00726_materialized_view_concurrent +00726_modulo_for_date +00733_if_datetime +00735_or_expr_optimize_bug +00737_decimal_group_by +00738_nested_merge_multidimensional_array +00740_optimize_predicate_expression +00745_compile_scalar_subquery +00746_compile_non_deterministic_function +00746_hashing_tuples +00747_contributors +00750_merge_tree_merge_with_o_direct +00751_hashing_ints +00752_low_cardinality_array_result +00752_low_cardinality_mv_1 +00752_low_cardinality_permute +00753_alter_destination_for_storage_buffer +00753_quantile_format +00753_with_with_single_alias +00754_alter_modify_column_partitions +00754_first_significant_subdomain_more +00755_avg_value_size_hint_passing +00756_power_alias +00757_enum_defaults_const +00757_enum_defaults_const_analyzer +00759_kodieg +00760_insert_json_with_defaults +00760_url_functions_overflow +00761_lower_utf8_bug +00765_sql_compatibility_aliases +00780_unaligned_array_join +00799_function_dry_run +00800_low_cardinality_array_group_by_arg +00800_low_cardinality_empty_array +00801_daylight_saving_time_hour_underflow +00802_daylight_saving_time_shift_backwards_at_midnight +00802_system_parts_with_datetime_partition +00803_odbc_driver_2_format +00803_xxhash +00804_rollup_with_having +00807_regexp_quote_meta +00810_in_operators_segfault +00812_prewhere_alias_array +00813_parse_date_time_best_effort_more +00814_parsing_ub +00816_join_column_names_sarg +00817_with_simple +00818_join_bug_4271 +00819_ast_refactoring_bugs +00820_multiple_joins +00820_multiple_joins_subquery_requires_alias +00822_array_insert_default +00823_sequence_match_dfa +00824_filesystem +00826_cross_to_inner_join +00829_bitmap64_function +00834_date_datetime_cmp +00834_not_between +00836_numbers_table_function_zero +00839_bitmask_negative +00840_top_k_weighted +00844_join_lightee2 +00845_join_on_aliases +00847_multiple_join_same_column +00853_join_with_nulls_crash +00854_multiple_join_asterisks +00856_no_column_issue_4242 +00859_distinct_with_join +00860_unknown_identifier_bug +00862_decimal_in +00870_t64_codec +00871_t64_codec_signed +00873_t64_codec_date +00874_issue_3495 +00876_wrong_arraj_join_column +00880_decimal_in_key +00881_unknown_identifier_in_in +00882_multiple_join_no_alias +00897_flatten +00898_quantile_timing_parameter_check +00901_joint_entropy +00902_entropy +00903_array_with_constant_function +00904_array_with_constant_2 +00905_compile_expressions_compare_big_dates +00905_field_with_aggregate_function_state +00906_low_cardinality_const_argument +00906_low_cardinality_rollup +00907_set_index_with_nullable_and_low_cardinality +00907_set_index_with_nullable_and_low_cardinality_bug +00908_analyze_query +00909_ngram_distance +00910_buffer_prewhere_different_types +00910_decimal_group_array_crash_3783 +00912_string_comparison +00914_join_bgranvea +00914_replicate +00915_simple_aggregate_function +00915_simple_aggregate_function_summing_merge_tree +00915_tuple_orantius +00916_add_materialized_column_after +00916_create_or_replace_view +00917_least_sqr +00917_multiple_joins_denny_crane +00919_histogram_merge +00919_sum_aggregate_states_constants +00920_multiply_aggregate_states_constants +00926_adaptive_index_granularity_collapsing_merge_tree +00926_adaptive_index_granularity_merge_tree +00926_adaptive_index_granularity_replacing_merge_tree +00926_adaptive_index_granularity_versioned_collapsing_merge_tree +00926_multimatch +00927_asof_join_noninclusive +00927_asof_joins +00928_multi_match_constant_constant +00930_arrayIntersect +00930_max_partitions_per_insert_block +00931_low_cardinality_nullable_aggregate_function_type +00931_low_cardinality_read_with_empty_array +00931_low_cardinality_set_index_in_key_condition +00932_array_intersect_bug +00932_geohash_support +00933_reserved_word +00933_ttl_with_default +00934_is_valid_utf8 +00935_to_iso_week_first_year +00936_crc_functions +00938_basename +00939_limit_by_offset +00939_test_null_in +00944_minmax_null +00950_default_prewhere +00950_test_gorilla_codec +00952_part_frozen_info +00954_resample_combinator +00956_join_use_nulls_with_array_column +00957_coalesce_const_nullable_crash +00960_eval_ml_method_const +00961_checksums_in_system_parts_columns_table +00961_visit_param_buffer_underflow +00962_enumNotExect +00962_visit_param_various +00963_startsWith_force_primary_key +00964_os_thread_priority +00966_invalid_json_must_not_parse +00968_roundAge +00969_roundDuration +00973_create_table_as_table_function +00974_bitmapContains_with_primary_key +00974_full_outer_join +00975_json_hang +00977_join_use_nulls_denny_crane +00978_ml_math +00978_sum_map_bugfix +00978_table_function_values_alias +00979_quantileExcatExclusive_and_Inclusive +00979_set_index_not +00979_toFloat_monotonicity +00980_crash_nullable_decimal +00980_full_join_crash_fancyqlx +00981_no_virtual_columns +00982_array_enumerate_uniq_ranked +00990_function_current_user +00994_table_function_numbers_mt +00995_optimize_read_in_order_with_aggregation +00996_limit_with_ties +00997_extract_all_crash_6627 +00997_trim +00999_settings_no_extra_quotes +01001_enums_in_in_section +01009_insert_select_data_loss +01009_insert_select_nicelulu +01010_partial_merge_join_const_and_lc +01011_group_uniq_array_memsan +01012_select_limit_x_0 +01013_hex_decimal +01013_hex_float +01015_array_split +01015_attach_part +01015_random_constant +01016_index_tuple_field_type +01016_null_part_minmax +01016_uniqCombined64 +01018_empty_aggregation_filling +01018_optimize_read_in_order_with_in_subquery +01019_array_fill +01020_function_array_compact +01020_function_char +01020_having_without_group_by +01025_array_compact_generic +01026_char_utf8 +01029_early_constant_folding +01030_concatenate_equal_fixed_strings +01030_final_mark_empty_primary_key +01031_pmj_new_any_semi_join +01031_semi_anti_join +01032_cityHash64_for_UUID +01032_cityHash64_for_decimal +01034_order_by_pk_prefix +01034_with_fill_and_push_down_predicate +01035_prewhere_with_alias +01040_h3_get_resolution +01041_h3_is_valid +01042_check_query_and_last_granule_size +01043_categorical_iv +01043_h3_edge_length_m +01044_great_circle_angle +01044_h3_edge_angle +01045_bloom_filter_null_array +01047_no_alias_columns_with_table_aliases +01047_nullable_rand +01047_simple_aggregate_sizes_of_columns_bug +01050_group_array_sample +01051_random_printable_ascii +01051_scalar_optimization +01053_if_chain_check +01055_prewhere_bugs +01056_negative_with_bloom_filter +01061_alter_codec_with_type +01062_pm_multiple_all_join_same_value +01063_create_column_set +01064_pm_all_join_const_and_nullable +01065_array_zip_mixed_const +01066_bit_count +01067_join_null +01068_parens +01069_set_in_group_by +01070_h3_get_base_cell +01070_h3_hex_area_m2 +01070_h3_indexes_are_neighbors +01070_h3_to_parent +01070_h3_to_string +01070_string_to_h3 +01071_in_array +01072_nullable_jit +01072_select_constant_limit +01073_blockSerializedSize +01073_crlf_end_of_line +01075_in_arrays_enmk +01076_array_join_prewhere_const_folding +01076_range_reader_segfault +01078_bloom_filter_operator_not_has +01079_bit_operations_using_bitset +01079_new_range_reader_segfault +01079_order_by_pk +01079_reinterpret_as_fixed_string +01081_keywords_formatting +01083_functional_index_in_mergetree +01083_log_first_column_alias +01085_datetime_arithmetic_preserve_timezone +01085_extract_all_empty +01085_simdjson_uint64 +01086_modulo_or_zero +01087_index_set_ubsan +01087_storage_generate +01088_array_slice_of_aggregate_functions +01090_fixed_string_bit_ops +01091_query_profiler_does_not_hang +01096_array_reduce_in_ranges +01096_block_serialized_state +01096_zeros +01097_one_more_range_reader_test_wide_part +01097_pre_limit +01100_split_by_string +01104_fixed_string_like +01105_string_like +01106_const_fixed_string_like +01107_join_right_table_totals +01109_sc0rp10_string_hash_map_zero_bytes +01112_check_table_with_index +01114_alter_modify_compact_parts +01114_clear_column_compact_parts +01115_prewhere_array_join +01116_cross_count_asterisks +01117_chain_finalize_bug +01117_greatest_least_case +01119_optimize_trivial_insert_select +01120_join_constants +01123_parse_date_time_best_effort_even_more +01124_view_bad_types +01136_multiple_sets +01137_order_by_func_final +01137_sample_final +01143_trivial_count_with_join +01144_join_rewrite_with_ambiguous_column_and_view +01144_multiple_joins_rewriter_v2_and_lambdas +01145_with_fill_const +01163_search_case_insensetive_utf8 +01186_conversion_to_nullable +01189_create_as_table_as_table_function +01197_summing_enum +01198_plus_inf +01199_url_functions_path_without_schema_yiurule +01212_empty_join_and_totals +01213_alter_rename_compact_part +01220_scalar_optimization_in_alter +01234_to_string_monotonic +01247_least_greatest_filimonov +01248_least_greatest_mixed_const +01250_fixed_string_comparison +01251_string_comparison +01252_weird_time_zone +01255_geo_types_livace +01260_ubsan_decimal_parse +01262_fractional_timezone_near_start_of_epoch +01262_low_cardinality_remove +01264_nested_baloo_bear +01266_default_prewhere_reqq +01268_mergine_sorted_limit +01269_alias_type_differs +01272_offset_without_limit +01272_totals_and_filter_bug +01273_lc_fixed_string_field +01275_alter_rename_column_default_expr +01276_alter_rename_column_materialized_expr +01276_random_string +01276_system_licenses +01277_large_tuples +01277_unixTimestamp64_compatibility +01278_alter_rename_combination +01278_variance_nonnegative +01279_dist_group_by +01280_min_map_max_map +01280_null_in +01280_unicode_whitespaces_lexer +01281_join_with_prewhere_fix +01281_sum_nullable +01283_max_threads_simple_query_optimization +01283_strict_resize_bug +01284_view_and_extremes_bug +01285_date_datetime_key_condition +01289_min_execution_speed_not_too_early +01290_empty_array_index_analysis +01291_aggregation_in_order +01292_quantile_array_bug +01293_external_sorting_limit_bug +01296_pipeline_stuck +01300_polygon_convex_hull +01303_polygons_equals +01305_array_join_prewhere_in_subquery +01307_polygon_perimeter +01308_row_policy_and_trivial_count_query +01312_case_insensitive_regexp +01314_position_in_system_columns +01315_count_distinct_return_not_nullable +01318_parallel_final_stuck +01321_aggregate_functions_of_group_by_keys +01321_monotonous_functions_in_order_by_bug +01323_too_many_threads_bug +01324_if_transform_strings_to_enum +01324_settings_documentation +01326_build_id +01326_fixed_string_comparison_denny_crane +01326_hostname_alias +01328_bad_peephole_optimization +01338_sha256_fixedstring +01338_uuid_without_separator +01341_datetime64_wrong_supertype +01345_array_join_LittleMaverick +01345_index_date_vs_datetime +01346_array_join_mrxotey +01347_partition_date_vs_datetime +01351_geohash_assert +01351_parse_date_time_best_effort_us +01352_add_datetime_bad_get +01353_nullable_tuple +01353_topk_enum +01354_order_by_tuple_collate_const +01354_tuple_low_cardinality_array_mapped_bug +01355_defaultValueOfArgumentType_bug +01355_ilike +01356_initialize_aggregation +01357_result_rows +01358_mutation_delete_null_rows +01359_codeql +01359_geodistance_loop +01360_division_overflow +01361_buffer_table_flush_with_materialized_view +01362_year_of_ISO8601_week_modificators_for_formatDateTime +01372_wrong_order_by_removal +01374_if_nullable_filimonov +01375_null_issue_3767 +01375_storage_file_write_prefix_csv_with_names +01375_storage_file_write_prefix_tsv_with_names +01376_array_fill_empty +01376_null_logical +01379_with_fill_several_columns +01380_nullable_state +01381_for_each_with_states +01385_not_function +01389_filter_by_virtual_columns +01390_check_table_codec +01390_remove_injective_in_uniq +01391_join_on_dict_crash +01392_column_resolve +01396_negative_datetime_saturate_to_zero +01398_in_tuple_func +01400_join_get_with_multi_keys +01403_datetime64_constant_arg +01407_lambda_arrayJoin +01409_topK_merge +01410_full_join_and_null_predicates +01410_nullable_key_and_index_negate_cond +01411_from_unixtime +01411_xor_itai_shirav +01412_mod_float +01412_optimize_deduplicate_bug +01413_if_array_uuid +01413_truncate_without_table_keyword +01414_bloom_filter_index_with_const_column +01414_push_predicate_when_contains_with_clause +01415_table_function_view +01416_join_totals_header_bug +01417_update_permutation_crash +01418_index_analysis_bug +01419_materialize_null +01419_skip_index_compact_parts +01420_logical_functions_materialized_null +01421_array_nullable_element_nullable_index +01422_array_nullable_element_nullable_index +01423_if_nullable_cond +01425_default_value_of_type_name +01426_geohash_constants +01427_pk_and_expression_with_different_type +01428_hash_set_nan_key +01430_fix_any_rewrite_aliases +01431_finish_sorting_with_consts +01431_utf8_ubsan +01432_parse_date_time_best_effort_timestamp +01434_netloc_fuzz +01440_big_int_exotic_casts +01440_big_int_shift +01440_to_date_monotonicity +01441_array_combinator +01450_set_null_const +01451_normalize_query +01452_normalized_query_hash +01453_fixsed_string_sort +01453_normalize_query_alias_uuid +01455_nullable_type_with_if_agg_combinator +01455_time_zones +01456_low_cardinality_sorting_bugfix +01456_min_negative_decimal_formatting +01457_compile_expressions_fuzzer +01457_order_by_limit +01457_order_by_nulls_first +01458_count_digits +01458_is_decimal_overflow +01458_named_tuple_millin +01459_decimal_casts +01460_allow_dollar_and_number_in_identifier +01460_mark_inclusion_search_crash +01470_columns_transformers2 +01471_limit_by_format +01471_with_format +01474_decimal_scale_bug +01475_fix_bigint_shift +01475_mutation_with_if +01479_cross_join_9855 +01480_binary_operator_monotonicity +01481_join_with_materialized +01485_256_bit_multiply +01490_nullable_string_to_enum +01491_nested_multiline_comments +01492_array_join_crash_13829 +01492_format_readable_quantity +01493_table_function_null +01495_subqueries_in_with_statement_2 +01495_subqueries_in_with_statement_4 +01496_signedness_conversion_monotonicity +01497_alias_on_default_array +01497_extract_all_groups_empty_match +01497_now_support_timezone +01502_bar_overflow +01503_fixed_string_primary_key +01503_if_const_optimization +01504_view_type_conversion +01508_explain_header +01511_different_expression_with_same_alias +01511_format_readable_timedelta +01511_prewhere_with_virtuals +01513_ilike_like_cache +01514_tid_function +01515_with_global_and_with_propagation +01518_cast_nullable_virtual_system_column +01518_filtering_aliased_materialized_column +01518_nullable_aggregate_states1 +01518_select_in_null +01521_alter_enum_and_reverse_read +01521_max_length_alias +01523_date_time_compare_with_date_literal +01523_interval_operator_support_string_literal +01532_min_max_with_modifiers +01532_tuple_with_name_type +01533_distinct_depends_on_max_threads +01533_distinct_nullable_uuid +01534_lambda_array_join +01537_fuzz_count_equal +01540_verbatim_partition_pruning +01543_parse_datetime_besteffort_or_null_empty_string +01544_errorCodeToName +01548_lzy305 +01550_mutation_subquery +01552_impl_aggfunc_cloneresize +01554_bloom_filter_index_big_integer_uuid +01554_interpreter_integer_float +01555_or_fill +01556_accurate_cast_or_null +01556_if_null +01560_DateTime_and_DateTime64_comparision +01560_cancel_agg_func_combinator_native_name_constraint +01560_monotonicity_check_multiple_args_bug +01561_Date_and_DateTime64_comparision +01561_aggregate_functions_of_key_with_join +01567_system_processes_current_database +01576_if_null_external_aggregation +01580_column_const_comparision +01582_any_join_supertype +01582_deterministic_function_with_predicate +01583_const_column_in_set_index +01585_fuzz_bits_with_bugfix +01592_length_map +01592_toUnixTimestamp_Date +01593_functions_in_order_by +01596_full_join_chertus +01600_encode_XML +01600_min_max_compress_block_size +01600_multiple_left_join_with_aliases +01600_remerge_sort_lowered_memory_bytes_ratio +01602_array_aggregation +01603_decimal_mult_float +01605_dictinct_two_level +01605_key_condition_enum_int +01614_with_fill_with_limit +01615_two_args_function_index_fix +01616_untuple_access_field +01620_fix_simple_state_arg_type +01621_decode_XML +01623_byte_size_const +01626_cnf_test +01631_date_overflow_as_partition_key +01632_nullable_string_type_convert_to_decimal_type +01632_select_all_syntax +01633_limit_fuzz +01634_summap_nullable +01635_nullable_fuzz +01637_nullable_fuzz3 +01638_div_mod_ambiguities +01646_fix_window_funnel_inconistency +01648_normalize_query_keep_names +01649_with_alias_key_condition +01650_expressions_merge_bug +01651_group_uniq_array_enum +01654_bar_nan +01655_quarter_modificator_for_formatDateTime +01655_test_isnull_mysql_dialect +01655_window_functions_bug +01655_window_functions_null +01656_ipv4_bad_formatting +01656_join_defaul_enum +01656_test_hex_mysql_dialect +01657_array_element_ubsan +01657_test_toHour_mysql_compatibility +01658_test_base64Encode_mysql_compatibility +01659_array_aggregation_ubsan +01659_test_base64Decode_mysql_compatibility +01660_join_or_all +01660_system_parts_smoke +01660_test_toDayOfYear_mysql_compatibility +01661_join_complex +01661_test_toDayOfWeek_mysql_compatibility +01662_join_mixed +01662_test_toDayOfMonth_mysql_compatibility +01663_aes_msan +01663_quantile_weighted_overflow +01663_test_toDate_mysql_compatibility +01664_array_slice_ubsan +01664_ntoa_aton_mysql_compatibility +01665_merge_tree_min_for_concurrent_read +01665_substring_ubsan +01666_date_lut_buffer_overflow +01666_great_circle_distance_ubsan +01668_avg_weighted_ubsan +01668_test_toMonth_mysql_dialect +01669_join_or_duplicates +01669_test_toYear_mysql_dialect +01670_sign_function +01670_test_repeat_mysql_dialect +01671_aggregate_function_group_bitmap_data +01671_test_toQuarter_mysql_dialect +01672_actions_dag_merge_crash +01672_test_toSecond_mysql_dialect +01673_test_toMinute_mysql_dialect +01674_filter_by_uint8 +01674_htm_xml_coarse_parse +01674_unicode_asan +01676_range_hashed_dictionary +01676_round_int_ubsan +01677_array_enumerate_bug +01678_great_circle_angle +01679_format_readable_time_delta_inf +01680_predicate_pushdown_union_distinct_subquery +01681_arg_min_max_if_fix +01684_geohash_ubsan +01685_json_extract_double_as_float +01690_quantilesTiming_ubsan +01692_DateTime64_from_DateTime +01698_fix_toMinute +01699_timezoneOffset +01700_deltasum +01700_mod_negative_type_promotion +01702_bitmap_native_integers +01702_rewrite_avg_for_algebraic_optimization +01702_toDateTime_from_string_clamping +01703_rewrite_aggregate_function_case_insensitive +01704_transform_with_float_key +01706_optimize_normalize_count_variants +01707_join_use_nulls +01710_aggregate_projection_with_grouping_set +01710_aggregate_projection_with_hashing +01710_aggregate_projection_with_monotonic_key_expr +01710_minmax_count_projection_constant_query +01710_minmax_count_projection_count_nullable +01710_minmax_count_projection_modify_partition_key +01710_normal_projection_fix1 +01710_normal_projection_format +01710_normal_projection_join_plan_fix +01710_normal_projection_with_query_plan_optimization +01710_projection_additional_filters +01710_projection_aggregate_functions_null_for_empty +01710_projection_aggregation_in_order +01710_projection_array_join +01710_projection_detach_part +01710_projection_external_aggregate +01710_projection_in_index +01710_projection_in_set +01710_projection_materialize_with_missing_columns +01710_projection_mutation +01710_projection_optimize_aggregators_of_group_by_keys +01710_projection_optimize_group_by_function_keys +01710_projection_part_check +01710_projection_pk_trivial_count +01710_projection_query_plan_optimization_misc +01710_projection_row_policy +01710_projection_with_ast_rewrite_settings +01710_projection_with_column_transformers +01710_projection_with_joins +01710_projection_with_nullable_keys +01710_projections_group_by_no_key +01711_cte_subquery_fix +01711_decimal_multiplication +01712_no_adaptive_granularity_vertical_merge +01716_array_difference_overflow +01718_subtract_seconds_date +01719_join_timezone +01720_union_distinct_with_limit +01732_explain_syntax_union_query +01733_transform_ubsan +01735_join_get_low_card_fix +01735_to_datetime64 +01736_null_as_default +01744_tuple_cast_to_map_bugfix +01746_convert_type_with_default +01746_lc_values_format_bug +01746_test_for_tupleElement_must_be_constant_issue +01747_transform_empty_arrays +01748_partition_id_pruning +01753_mutate_table_predicated_with_table +01760_ddl_dictionary_use_current_database_name +01761_cast_to_enum_nullable +01761_round_year_bounds +01762_datetime64_extended_parsing +01763_filter_push_down_bugs +01763_support_map_lowcardinality_type +01764_collapsing_merge_adaptive_granularity +01764_table_function_dictionary +01765_move_to_table_overlapping_block_number +01765_tehran_dst +01766_todatetime64_no_timezone_arg +01768_array_product +01769_extended_range_2 +01770_add_months_ubsan +01770_extended_range_3 +01771_datetime64_no_time_part +01772_intdiv_minus_one_ubsan +01772_to_start_of_hour_align +01773_case_sensitive_revision +01773_case_sensitive_version +01773_min_max_time_system_parts_datetime64 +01774_case_sensitive_connection_id +01774_ip_address_in_range_2 +01774_tuple_null_in +01778_where_with_column_name +01779_quantile_deterministic_msan +01780_column_sparse_distinct +01780_column_sparse_filter +01780_column_sparse_pk +01780_column_sparse_tuple +01780_dict_get_or_null +01781_map_op_ubsan +01781_merge_tree_deduplication +01781_token_extractor_buffer_overflow +01783_merge_engine_join_key_condition +01785_pmj_lc_bug +01786_group_by_pk_many_streams +01787_arena_assert_column_nothing +01795_TinyLog_rwlock_ub +01796_Log_rwlock_ub +01797_StripeLog_rwlock_ub +01798_uniq_theta_union_intersect_not +01800_log_nested +01803_const_nullable_map +01803_untuple_subquery +01809_inactive_parts_to_delay_throw_insert +01811_datename +01811_filter_by_null +01812_has_generic +01813_quantileBfloat16_nans +01818_case_float_value_fangyc +01818_move_partition_simple +01820_unhex_case_insensitive +01821_to_date_time_ubsan +01822_short_circuit +01822_union_and_constans_error +01825_json_type_18 +01825_json_type_9 +01825_json_type_bools +01825_json_type_order_by +01825_json_type_partitions +01831_max_streams +01832_memory_write_suffix +01833_test_collation_alvarotuso +01835_alias_to_primary_key_cyfdecyf +01836_date_time_keep_default_timezone_on_operations_den_crane +01837_cast_to_array_from_empty_array +01838_system_dictionaries_virtual_key_column +01839_join_to_subqueries_rewriter_columns_matcher +01840_tupleElement_formatting_fuzzer +01845_add_testcase_for_arrayElement +01846_alter_column_without_type_bugfix +01846_null_as_default_for_insert_select +01851_fix_row_policy_empty_result +01851_s2_to_geo +01852_cast_operator_2 +01852_cast_operator_3 +01852_cast_operator_4 +01852_jit_if +01852_s2_get_neighbours +01855_jit_comparison_constant_result +01865_aggregator_overflow_row +01866_aggregate_function_interval_length_sum +01866_bit_positions_to_array +01866_datetime64_cmp_with_constant +01867_fix_storage_memory_mutation +01867_support_datetime64_version_column +01868_order_by_fill_with_datetime64 +01869_function_modulo_legacy +01869_reinterpret_as_fixed_string_uuid +01871_merge_tree_compile_expressions +01881_aggregate_functions_versioning +01881_create_as_tuple +01881_to_week_monotonic_fix +01881_total_bytes_storage_buffer +01881_union_header_mismatch_bug +01889_tokenize +01891_not_in_partition_prune +01891_not_like_partition_prune +01891_partition_by_uuid +01906_h3_to_geo +01906_lc_in_bug +01906_partition_by_multiply_by_zero +01908_with_unknown_column +01909_mbtolou +01911_logical_error_minus +01912_bad_cast_join_fuzz +01913_fix_column_transformer_replace_format +01913_if_int_decimal +01913_join_push_down_bug +01913_summing_mt_and_simple_agg_function_with_lc +01914_index_bgranvea +01914_ubsan_quantile_timing +01915_json_extract_raw_string +01916_low_cardinality_interval +01916_lowcard_dict_type +01916_multiple_join_view_optimize_predicate_chertus +01917_prewhere_column_type +01922_array_join_with_index +01925_date_date_time_comparison +01925_json_as_string_data_in_square_brackets +01925_merge_prewhere_table +01925_test_group_by_const_consistency +01926_bin_unbin +01926_date_date_time_supertype +01926_order_by_desc_limit +01926_union_all_schmak +01932_alter_index_with_order +01936_empty_function_support_uuid +01936_quantiles_cannot_return_null +01938_joins_identifiers +01940_pad_string +01940_totimezone_operator_monotonicity +01941_dict_get_has_complex_single_key +01942_untuple_transformers_msan +01943_log_column_sizes +01946_profile_sleep +01947_mv_subquery +01948_heredoc +01960_lambda_precedence +02001_join_on_const +02001_select_with_filter +02002_sampling_and_unknown_column_bug +02003_bug_from_23515 +02006_todatetime64_from_string +02006_use_constants_in_with_and_select +02013_emptystring_cast +02015_division_by_nullable +02015_order_by_with_fill_misoptimization +02016_order_by_with_fill_monotonic_functions_removal +02017_columns_with_dot_2 +02017_order_by_with_fill_redundant_functions +02019_multiple_weird_with_fill +02020_cast_integer_overflow +02020_exponential_smoothing_cross_block +02021_map_bloom_filter_index +02021_map_has +02021_prewhere_always_true_where +02022_array_full_text_bloom_filter_index +02023_parser_number_binary_literal +02025_having_filter_column +02025_subcolumns_compact_parts +02026_accurate_cast_or_default +02026_arrayDifference_const +02027_arrayCumSumNonNegative_const +02028_system_data_skipping_indices_size +02028_tokens +02030_function_mapContainsKeyLike +02030_quantiles_underflow +02032_short_circuit_least_greatest_bug +02036_jit_short_circuit +02041_conversion_between_date32_and_datetime64 +02041_openssl_hash_functions_test +02042_map_get_non_const_key +02045_like_function +02047_alias_for_table_and_database_name +02053_INSERT_SELECT_MATERIALIZED +02095_function_get_os_kernel_version +02096_date_time_1970_saturation2 +02097_initializeAggregationNullable +02098_date32_comparison +02100_limit_push_down_bug +02100_now64_types_bug +02100_replaceRegexpAll_bug +02111_function_mapExtractKeyLike +02111_with_fill_no_rows +02112_skip_index_set_and_or +02113_base64encode_trailing_bytes_1 +02113_untuple_func_alias +02118_show_create_table_rocksdb +02124_empty_uuid +02124_uncompressed_cache +02125_low_cardinality_int256 +02125_transform_decimal_bug +02126_lc_window_functions +02129_window_functions_disable_optimizations +02131_materialize_column_cast +02131_remove_columns_in_subquery +02131_row_policies_combination +02131_skip_index_not_materialized +02132_empty_mutation_livelock +02133_final_prewhere_where_lowcardinality_replacing +02148_cast_type_parsing +02148_issue_32737 +02149_issue_32487 +02150_replace_regexp_all_empty_match +02151_lc_prefetch +02151_replace_regexp_all_empty_match_alternative +02152_count_distinct_optimization +02152_short_circuit_throw_if +02154_bitmap_contains +02155_nested_lc_defalut_bug +02155_parse_date_lowcard_default_throw +02156_storage_merge_prewhere_not_ready_set_bug +02157_line_as_string_output_format +02158_interval_length_sum +02160_h3_hex_area_Km2 +02160_monthname +02160_special_functions +02161_array_first_last +02162_array_first_last_index +02163_operators +02165_h3_edge_length_km +02167_columns_with_dots_default_values +02169_fix_view_offset_limit_setting +02176_toStartOfWeek_overflow_pruning +02178_column_function_insert_from +02179_bool_type +02179_key_condition_no_common_type +02180_group_by_lowcardinality +02187_test_final_and_limit_modifier +02188_table_function_format +02189_join_type_conversion +02190_current_metrics_query +02191_parse_date_time_best_effort_more_cases +02205_map_populate_series_non_const +02205_postgresql_functions +02206_array_starts_ends_with +02207_key_condition_floats +02207_ttl_move_if_exists +02209_short_circuit_node_without_parents +02210_append_to_dev_dull +02210_toColumnTypeName_toLowCardinality_const +02212_cte_and_table_alias +02220_array_join_format +02224_s2_test_const_columns +02226_low_cardinality_text_bloom_filter_index +02232_partition_pruner_mixed_constant_type +02232_partition_pruner_single_point +02233_optimize_aggregation_in_order_prefix_with_merge +02234_position_case_insensitive_utf8 +02240_asof_join_biginteger +02240_get_type_serialization_streams +02241_array_first_last_or_null +02241_short_circuit_short_column +02242_if_then_else_null_bug +02242_negative_datetime64 +02242_optimize_to_subcolumns_no_storage +02242_throw_if_constant_argument +02243_in_ip_address +02243_ipv6_long_parsing +02245_format_string_stack_overflow +02245_join_with_nullable_lowcardinality_crash +02247_fix_extract_parser +02248_nullable_custom_types_to_string +02251_last_day_of_month +02265_cross_join_empty_list +02265_limit_push_down_over_window_functions_bug +02267_output_format_prometheus +02267_special_operator_parse_alias_check +02267_type_inference_for_insert_into_function_null +02277_full_sort_join_misc +02285_hex_bin_support_more_types +02286_convert_decimal_type +02286_function_wyhash +02292_hash_array_tuples +02293_ilike_on_fixed_strings +02293_optimize_aggregation_in_order_Array_functions +02294_optimize_aggregation_in_order_prefix_Array_functions +02294_system_certificates +02295_global_with_in_subquery +02296_nullable_arguments_in_array_filter +02302_clash_const_aggegate_join +02304_grouping_set_order_by +02306_window_move_row_number_fix +02307_join_get_array_null +02310_generate_multi_columns_with_uuid +02310_uuid_v7 +02312_is_not_null_prewhere +02313_cross_join_dup_col_names +02313_dump_column_structure_low_cardinality +02313_multiple_limits +02313_negative_datetime64 +02313_test_fpc_codec +02315_pmj_union_ubsan_35857 +02315_replace_multiif_to_if +02316_const_string_intersact +02316_literal_no_octal +02316_values_table_func_bug +02320_alter_columns_with_dots +02320_mapped_array_witn_const_nullable +02321_nested_short_circuit_functions +02325_dates_schema_inference +02326_settings_changes_system_table +02336_sort_optimization_with_fill +02337_multiple_joins_original_names +02338_analyzer_constants_basic +02340_analyzer_functions +02343_analyzer_lambdas_issue_28083 +02343_analyzer_lambdas_issue_36677 +02345_analyzer_subqueries +02345_create_table_allow_trailing_comma +02345_partial_sort_transform_optimization +02346_position_countsubstrings_zero_byte +02346_system_tokenizers +02346_text_index_bug47393 +02346_text_index_bug54541 +02346_text_index_bug84805 +02346_text_index_bug87887 +02346_text_index_bug93432 +02346_text_index_byte_size_reporting +02346_text_index_collapsingmergetree +02346_text_index_default_granularity +02346_text_index_detach_attach +02346_text_index_dictionary_frontcoding +02346_text_index_direct_read_crash +02346_text_index_direct_read_nullable +02346_text_index_direct_read_with_query_condition_cache +02346_text_index_functions_with_empty_needle +02346_text_index_materialize_subcolumns +02346_to_hour_monotonicity_fix_2 +02347_rank_corr_nan +02347_rank_corr_size_overflow +02351_Map_combinator_dist +02353_ascii +02353_isnullable +02353_partition_prune_nullable_key +02354_array_lowcardinality +02354_numeric_literals_with_underscores +02354_tuple_element_with_default +02354_tuple_lowcardinality +02354_vector_search_and_other_skipping_indexes +02354_vector_search_default_granularity +02354_vector_search_multiple_indexes +02354_vector_search_subquery +02354_vector_search_unquoted_index_parameters +02355_column_type_name_lc +02355_control_block_size_in_aggregator +02360_small_notation_h_for_hour_interval +02363_mapupdate_improve +02364_window_case +02366_asof_optimize_predicate_bug_37813 +02366_explain_query_tree +02366_normalize_aggregate_function_types_and_states +02367_analyzer_table_alias_columns +02367_optimize_trivial_count_with_array_join +02368_analyzer_table_functions +02370_extractAll_regress +02371_select_projection_normal_agg +02374_combine_multi_if_and_count_if_opt +02374_in_tuple_index +02374_regexp_replace +02375_double_escaping_json +02375_scalar_lc_cte +02380_analyzer_join_sample +02381_parseDateTime64BestEffortUS +02383_schema_inference_hints +02386_analyzer_in_function_nested_subqueries +02387_analyzer_cte +02392_every_setting_must_have_documentation +02393_every_metric_must_have_documentation +02394_every_profile_event_must_have_documentation +02395_every_merge_tree_setting_must_have_documentation +02398_subquery_where_pushdown_and_limit_offset +02401_merge_tree_old_tmp_dirs_cleanup +02405_pmj_issue_40335 +02406_try_read_datetime64_bug +02408_to_fixed_string_short_circuit +02409_url_format_detection +02410_csv_empty_fields_inference +02410_to_decimal_or_default +02414_all_new_table_functions_must_be_documented +02415_all_new_functions_must_be_documented +02416_in_set_same_ast_diff_columns +02416_row_policy_always_false_index +02417_from_select_syntax +02418_tautological_if_index +02420_key_condition_actions_dag_bug_40599 +02421_decimal_in_precision_issue_41125 +02423_json_quote_float64 +02426_to_string_nullable_fixedstring +02427_column_nullable_ubsan +02427_msan_group_array_resample +02428_batch_nullable_assert +02428_delete_with_settings +02428_index_analysis_with_null_literal +02429_combinators_in_array_reduce +02429_offset_pipeline_stuck_bug +02430_initialize_aggregation_with_combinators +02431_single_value_or_null_empty +02452_check_low_cardinality +02454_compressed_marks_in_compact_part +02455_extract_fixed_string_from_nested_json +02456_BLAKE3_hash_function_test +02456_aggregate_state_conversion +02457_datediff_via_unix_epoch +02457_key_condition_with_types_that_cannot_be_nullable +02457_morton_coding_with_mask +02460_prewhere_row_level_policy +02462_distributions +02467_cross_join_three_table_functions +02467_set_with_lowcardinality_type +02468_has_any_tuple +02471_wrong_date_monotonicity +02473_extract_low_cardinality_from_json +02473_map_element_nullable +02474_extract_fixedstring_from_json +02474_timeDiff_UTCTimestamp +02474_unhex_in_fix_string +02475_analyzer_join_tree_subquery +02475_analyzer_subquery_compound_expression +02475_bad_cast_low_cardinality_to_string_bug +02475_date_time_schema_inference_bug +02475_or_function_alias_and_const_where +02475_positive_modulo +02476_analyzer_join_with_unused_columns +02477_analyzer_ast_key_condition_crash +02477_exists_fuzz_43478 +02477_fuse_quantiles +02477_is_null_parser +02477_logical_expressions_optimizer_issue_89803 +02478_analyzer_table_expression_aliases +02479_analyzer_aggregation_crash +02479_analyzer_aggregation_totals_rollup_crash_fix +02479_if_with_null_and_cullable_const +02479_nullable_primary_key_non_first_column +02480_every_asynchronous_metric_must_have_documentation +02480_interval_casting_and_subquery +02480_max_map_null_totals +02480_parse_date_time_best_effort_math_overflow +02481_aggregation_in_order_plan +02481_analyzer_optimize_grouping_sets_keys +02481_low_cardinality_with_short_circuit_functins_mutations +02481_xxh3_hash_function +02482_if_with_nothing_argument +02489_analyzer_indexes +02493_analyzer_sum_if_to_count_if +02493_analyzer_table_functions_untuple +02494_analyzer_cte_resolution_in_subquery_fix +02494_array_function_range +02494_parser_string_binary_literal +02494_query_cache_empty_tuple +02495_sum_if_to_count_if_bug +02497_analyzer_sum_if_count_if_pass_crash_fix +02497_storage_join_right_assert +02498_analyzer_aggregate_functions_arithmetic_operations_pass_fix +02499_analyzer_set_index +02499_escaped_quote_schema_inference +02499_quantile_nan_ubsan_msan +02500_analyzer_storage_view_crash_fix +02501_analyzer_expired_context_crash_fix +02502_analyzer_insert_select_crash_fix +02503_in_lc_const_args_bug +02503_join_switch_alias_fuzz +02504_bar_fractions +02504_explain_ast_insert +02506_date_time64_floating_point_negative_value +02508_index_analysis_to_date_timezone +02509_h3_arguments +02510_group_by_prewhere_null +02512_array_join_name_resolution +02513_analyzer_duplicate_alias_crash_fix +02513_analyzer_sort_msan +02513_broken_datetime64_init_on_mac +02514_if_with_lazy_low_cardinality +02514_null_dictionary_source +02514_tsv_zero_started_number +02515_aggregate_functions_statistics +02515_analyzer_null_for_empty +02515_and_or_if_multiif_not_return_lc +02515_distinct_zero_size_key_bug_44831 +02515_generate_ulid +02515_projections_with_totals +02516_projections_with_rollup +02517_union_columns_order +02517_uuid_parsing +02518_merge_engine_nullable_43324 +02518_qualified_asterisks_alias_table_name +02519_monotonicity_fuzz +02521_analyzer_aggregation_without_column +02521_cannot_find_column_in_projection +02523_range_const_start +02524_fuzz_and_fuss +02524_fuzz_and_fuss_2 +02525_analyzer_function_in_crash_fix +02525_jit_logical_functions_nan +02525_range_hashed_dictionary_update_field +02526_merge_join_int_decimal +02530_ip_part_id +02531_semi_join_null_const_bug +02532_analyzer_aggregation_with_rollup +02532_profileevents_server_startup_time +02533_generate_random_schema_inference +02535_analyzer_limit_offset +02535_ip_parser_not_whole +02536_replace_with_nonconst_needle_and_replacement +02537_system_formats +02538_analyzer_create_table_as_select +02538_ngram_bf_index_with_null +02538_nullable_array_tuple_timeseries +02539_generate_random_ip +02539_generate_random_low_cardinality +02539_generate_random_map +02540_date_column_consistent_insert_behaviour +02541_analyzer_grouping_sets_crash_fix +02541_empty_function_support_ip +02541_multiple_ignore_with_nested_select +02541_tuple_element_with_null +02542_case_no_else +02542_table_function_format +02551_ipv4_implicit_uint64 +02552_inner_join_with_where_true +02552_regression_crash +02552_sparse_columns_intersect +02553_json_type_attach_partition +02554_format_json_columns_for_empty +02559_add_parts +02559_ip_types_bloom +02559_multiple_read_steps_in_prewhere_fuzz +02559_multiple_read_steps_in_prewhere_missing_columns_2 +02559_nested_multiple_levels_default +02560_analyzer_materialized_view +02560_count_digits +02560_null_as_default +02560_quantile_min_max +02561_sorting_constants_and_distinct_crash +02564_analyzer_ssb_cross_to_inner +02564_date_format +02564_read_in_order_final_desc +02565_update_empty_nested +02567_and_consistency +02568_and_consistency +02568_array_map_const_low_cardinality +02569_order_by_aggregation_result +02572_max_intersections +02576_predicate_push_down_sorting_fix +02577_analyzer_array_join_calc_twice +02578_ipv4_codec_t64 +02580_like_substring_search_bug +02582_async_reading_with_small_limit +02584_range_ipv4 +02587_csv_big_numbers_inference +02591_bson_long_tuple +02662_sparse_columns_mutations_4 +02662_sparse_columns_mutations_5 +02668_column_block_number_with_projections +02674_and_consistency +02674_date_int_string_json_inference +02674_null_default_structure +02675_is_ipv6_function_fix +02676_distinct_reading_in_order_analyzer +02677_decode_url_component +02677_get_subcolumn_array_of_tuples +02677_grace_hash_limit_race +02679_explain_merge_tree_prewhere_row_policy +02680_instr_alias_for_position_case_insensitive +02681_aggregation_by_partitions_bug +02681_comparsion_tuple_elimination_ast +02690_subquery_identifiers +02691_multiple_joins_backtick_identifiers +02692_multiple_joins_unicode +02697_alter_dependencies +02698_marked_dropped_tables +02699_polygons_sym_difference_rollup +02699_polygons_sym_difference_total_analyzer +02700_regexp_operator +02702_logical_optimizer_with_nulls +02705_grouping_keys_equal_keys +02705_projection_and_ast_optimizations_bug +02707_analyzer_nested_lambdas_types +02708_parallel_replicas_not_found_column +02709_generate_random_valid_decimals_and_bools +02709_storage_memory_compressed +02710_aggregation_nested_map_ip_uuid +02710_date_diff_aliases +02710_topk_with_empty_array +02711_trim_aliases +02713_array_low_cardinality_string +02713_ip4_uint_compare +02714_date_date32_in +02715_or_null +02716_int256_arrayfunc +02717_pretty_json +02719_aggregate_with_empty_string_key +02720_row_policy_column_with_dots +02724_function_in_left_table_clause_asof_join +02724_jit_logical_functions +02724_mutliple_storage_join +02724_persist_interval_type +02725_agg_projection_respect_PK +02725_alias_with_restricted_keywords +02725_cnf_large_check +02730_dictionary_hashed_load_factor_element_count +02731_auto_convert_dictionary_layout_to_complex_by_complex_keys +02731_in_operator_with_one_size_tuple +02733_distinct +02733_fix_distinct_in_order_bug_49622 +02733_sparse_columns_reload +02734_big_int_from_float_ubsan +02734_optimize_group_by +02734_sparse_columns_short_circuit +02735_array_map_array_of_tuples +02736_bit_count_big_int +02746_index_analysis_binary_operator_with_null +02751_match_constant_needle +02751_multiif_to_if_crash +02751_parallel_replicas_bug_chunkinfo_not_set +02752_custom_separated_ignore_spaces_bug +02752_is_null_priority +02763_jit_compare_functions_nan +02763_mutate_compact_part_with_skip_indices_and_projections +02764_index_analysis_fix +02764_parallel_replicas_plain_merge_tree +02765_parallel_replicas_final_modifier +02766_bitshift_with_const_arguments +02769_compare_functions_nan +02770_jit_aggregation_nullable_key_fix +02771_if_constant_folding +02771_jit_functions_comparison_crash +02771_log_faminy_truncate_count +02782_inconsistent_formatting_and_constant_folding +02782_values_null_to_lc_nullable +02783_date_predicate_optimizations +02784_move_all_conditions_to_prewhere_analyzer_asan +02784_projections_read_in_order_bug +02784_schema_inference_null_as_default +02785_date_predicate_optimizations_ast_query_tree_rewrite +02785_summing_merge_tree_datetime64 +02786_transform_float +02787_transform_null +02789_functions_after_sorting_and_columns_with_same_names_bug +02789_functions_after_sorting_and_columns_with_same_names_bug_2 +02789_jit_cannot_convert_column +02789_set_index_nullable_condition_bug +02790_fix_coredump_when_compile_expression +02790_keyed_hash_bug +02790_url_multiple_tsv_files +02795_full_join_assert_cast +02796_projection_date_filter_on_view +02797_aggregator_huge_mem_usage_bug +02797_transform_narrow_types +02798_generic_transform +02799_transform_empty_arrays +02801_transform_nullable +02802_with_cube_with_totals +02804_intersect_bad_cast +02806_cte_block_cannot_be_empty +02807_lower_utf8_msan +02807_math_unary_crash +02808_aliases_inside_case +02809_has_subsequence +02809_has_token +02809_prewhere_and_in +02810_initcap +02810_row_binary_with_defaults +02810_system_jemalloc_bins +02811_insert_schema_inference +02811_parallel_replicas_prewhere_count +02811_read_in_order_and_array_join_bug +02812_bug_with_unused_join_columns +02812_csv_date_time_with_comma +02812_from_to_utc_timestamp +02812_large_varints +02812_subquery_operators +02813_any_value +02813_array_agg +02813_float_parsing +02813_func_today_and_alias +02813_system_licenses_base +02814_order_by_tuple_window_function +02815_alias_to_length +02815_empty_subquery_nullable_bug +02815_first_line +02815_fix_not_found_constants_col_in_block +02816_has_token_empty +02818_parameterized_view_with_cte_multiple_usage +02828_create_as_table_function_rename +02831_ast_fuzz_asan_join +02831_trash +02832_integer_type_inference +02832_transform_fixed_string_no_default +02833_array_join_columns +02833_sparse_columns_tuple_function +02834_array_exists_segfault +02834_sparse_columns_sort_with_limit +02835_nested_array_lowcardinality +02841_group_array_sorted +02841_join_filter_set_sparse +02841_not_ready_set_join_on +02841_tuple_modulo +02841_with_clause_resolve +02843_date_predicate_optimizations_bugs +02845_domain_rfc_support_ipv6 +02845_join_on_cond_sparse +02845_prewhere_preserve_column +02861_filter_pushdown_const_bug +02861_interpolate_alias_precedence +02861_uuid_format_serialization +02862_uuid_reinterpret_as_numeric +02863_decode_html_component +02863_mutation_where_in_set_result_cache_pipeline_stuck_bug +02864_filtered_url_with_globs +02864_profile_event_part_lock +02864_statistics_bug_69589 +02864_test_ipv4_type_mismatch +02866_size_of_marks_skip_idx_explain +02867_null_lc_in_bug +02867_nullable_primary_key_final +02868_distinct_to_count_optimization +02869_unicode_minus +02871_join_on_system_errors +02874_infer_objects_as_named_tuples +02874_parse_json_as_json_each_row_on_no_metadata +02875_final_invalid_read_ranges_bug +02875_fix_column_decimal_serialization +02875_json_array_as_string +02876_json_incomplete_types_as_strings_inference +02876_sort_union_of_sorted +02882_primary_key_index_in_function_different_types +02883_read_in_reverse_order_virtual_column +02884_interval_operator_support_plural_literal +02884_parallel_window_functions_bug +02884_virtual_column_order_by +02886_binary_like +02888_integer_type_inference_in_if_function +02888_obsolete_settings +02888_single_state_nullable_type +02889_system_drop_format_schema +02890_partition_prune_in_extra_columns +02891_alter_update_adaptive_granularity +02891_functions_over_sparse_columns +02893_bad_sample_view +02893_trash_optimization +02895_cast_operator_bug +02896_optimize_array_exists_to_has_with_date +02900_decimal_sort_with_multiple_columns +02900_window_function_with_sparse_column +02902_show_databases_limit +02903_bug_43644 +02903_parameterized_view_explain_ast +02907_filter_pushdown_crash +02908_alter_column_alias +02910_nullable_enum_cast +02911_analyzer_remove_unused_projection_columns +02911_cte_invalid_query_analysis +02911_support_alias_column_in_indices +02911_system_symbols +02912_group_array_sample +02913_sum_map_state +02915_analyzer_fuzz_1 +02916_analyzer_set_in_join +02916_set_formatting +02917_transform_tsan +02919_ddsketch_quantile +02919_segfault_nullable_materialized_update +02920_fix_json_merge_patch +02920_unary_operators_functions +02921_bit_hamming_distance_big_int +02921_fuzzbits_with_array_join +02923_cte_equality_disjunction +02923_explain_expired_context +02923_join_use_nulls_modulo +02931_alter_materialized_view_query_inconsistent +02931_ubsan_error_arena_aligned_alloc +02932_non_ready_set_stuck +02932_parallel_replicas_fuzzer +02932_set_ttl_where +02933_compare_with_bool_as_string +02933_ephemeral_mv +02935_ipv6_bit_operations +02935_ipv6_from_uint128_equality +02935_ipv6_from_uint128_one +02935_ipv6_from_uint128_two +02935_ipv6_from_uint128_with_bit_and +02935_ipv6_to_and_from_uint128 +02941_any_RESPECT_NULL_sparse_column +02943_create_query_interpreter_sample_block_fix +02943_exprs_order_in_group_by_with_rollup +02943_positional_arguments_bugs +02943_tokenbf_and_ngrambf_indexes_support_match_function +02943_use_full_text_skip_index_with_has_any +02943_variant_element +02944_variant_as_common_type_analyzer +02945_blake3_msan +02946_literal_alias_misclassification +02946_merge_tree_final_split_ranges_by_primary_key +02947_dropped_tables_parts +02947_parallel_replicas_remote +02949_parallel_replicas_scalar_subquery_big_integer +02949_ttl_group_by_bug +02950_reading_array_tuple_subcolumns +02953_slow_create_view +02955_avro_format_zstd_encode_support +02955_sparkBar_alias_sparkbar +02956_fix_to_start_of_milli_microsecond +02956_format_constexpr +02959_system_database_engines +02961_sumMapFiltered_keepKey +02962_analyzer_const_in_count_distinct +02962_analyzer_constant_set +02962_indexHint_rpn_construction +02962_max_joined_block_rows +02962_parallel_window_functions_different_partitioning +02963_single_value_destructor +02965_projection_with_partition_pruning +02966_float32_promotion +02967_index_hint_crash +02968_analyzer_join_column_not_found +02968_full_sorting_join_fuzz +02968_sumMap_with_nan +02968_url_args +02969_analyzer_eliminate_injective_functions +02969_functions_to_subcolumns_if_null +02970_generate_series +02971_functions_to_subcolumns_column_names +02971_functions_to_subcolumns_map +02971_functions_to_subcolumns_variant +02974_analyzer_array_join_subcolumn +02974_if_with_map +02975_intdiv_with_decimal +02981_translate_fixedstring +02982_create_mv_inner_extra +02982_dont_infer_exponent_floats +02983_empty_map_hasToken +02985_if_over_big_int_decimal +02986_leftpad_fixedstring +02987_group_array_intersect +02987_logical_optimizer_pass_lowcardinality +02989_group_by_tuple +02990_optimize_uniq_to_count_alias +02990_parts_splitter_invalid_ranges +02991_count_rewrite_analyzer +02992_all_columns_should_have_comment +02993_lazy_index_loading +02994_cosineDistanceNullable +02995_preliminary_filters_duplicated_columns +02995_preliminary_filters_duplicated_columns_SimpleAggregateFunction +02995_ulid_short_circuit +02996_index_compaction_counterexample +02997_fix_datetime64_scale_conversion +02998_analyzer_prewhere_report +02998_ipv6_hashing +02998_pretty_format_print_readable_number_if_last_column +02998_primary_key_skip_columns +02998_system_dns_cache_table +02999_scalar_subqueries_bug_2 +02999_ulid_short_circuit +03000_minmax_index_first +03000_virtual_columns_in_prewhere +03001_analyzer_nullable_nothing +03001_block_offset_column_2 +03001_consider_lwd_when_merge +03001_data_version_column +03002_analyzer_prewhere +03002_map_array_functions_with_low_cardinality +03002_modify_query_cte +03002_sample_factor_where +03003_count_asterisk_filter +03003_enum_and_string_compatible +03003_sql_json_nonsense +03006_buffer_overflow_join +03006_mv_deduplication_throw_if_async_insert +03006_parallel_replicas_cte_explain_syntax_crash +03007_column_nullable_uninitialzed_value +03008_filter_projections_non_deterministoc_functions +03008_groupSortedArray_field +03008_index_small +03008_uniq_exact_equal_ranges +03009_consecutive_keys_nullable +03009_range_dict_get_or_default +03010_read_system_parts_table_test +03010_sum_to_to_count_if_nullable +03010_view_prewhere_in +03010_virtual_memory_mappings_asynchronous_metrics +03011_adaptative_timeout_compatibility +03013_addDays_with_timezone +03013_fuzz_arrayPartialReverseSort +03013_ignore_drop_queries_probability +03013_position_const_start_pos +03013_repeat_with_nonnative_integers +03014_analyzer_group_by_use_nulls +03014_analyzer_groupby_fuzz_60317 +03015_aggregator_empty_data_multiple_blocks +03015_analyzer_groupby_fuzz_60772 +03015_peder1001 +03016_analyzer_groupby_fuzz_59796 +03017_analyzer_groupby_fuzz_61600 +03018_analyzer_greater_null +03023_analyzer_optimize_group_by_function_keys_with_nulls +03023_remove_unused_column_distinct +03031_input_format_allow_errors_num_bad_escape_sequence +03031_low_cardinality_logical_error +03031_table_function_fuzzquery +03032_multi_search_const_low_cardinality +03032_numbers_zeros +03032_redundant_equals +03032_scalars_create_as_select +03032_variant_bool_number_not_suspicious +03033_analyzer_parametrized_view_alias +03033_analyzer_resolve_from_parent_scope +03033_create_as_copies_comment +03033_cte_numbers_memory +03033_distinct_transform_const_columns +03033_final_undefined_last_mark +03033_from_unixtimestamp_joda_by_int64 +03033_parts_splitter_bug_and_index_loading +03033_scalars_context_data_race +03033_virtual_column_override +03033_with_fill_interpolate +03034_json_extract_variant +03035_argMinMax_numeric_non_extreme_bug +03035_materialized_primary_key +03035_morton_encode_no_rows +03036_prewhere_lambda_function +03036_with_numbers +03037_dot_product_overflow +03037_precent_rank +03037_union_view +03038_ambiguous_column +03038_move_partition_to_oneself_deadlock +03039_unknown_identifier_window_function +03040_alias_column_join +03040_array_sum_and_join +03040_dynamic_type_alters_1_compact_merge_tree +03040_dynamic_type_alters_1_memory +03040_dynamic_type_alters_1_wide_merge_tree +03040_dynamic_type_alters_2_compact_merge_tree +03041_analyzer_gigachad_join +03041_select_with_query_result +03042_analyzer_alias_join +03042_not_found_column_c1 +03043_group_array_result_is_expected +03044_analyzer_alias_join +03044_array_join_columns_in_nested_table +03045_analyzer_alias_join_with_if +03046_column_in_block_array_join +03047_analyzer_alias_join +03047_group_by_field_identified_aggregation +03048_not_found_column_xxx_in_block +03049_analyzer_group_by_alias +03050_select_one_one_one +03051_many_ctes +03052_query_hash_includes_aliases +03054_analyzer_join_alias +03055_analyzer_subquery_group_array +03057_analyzer_subquery_alias_join +03059_analyzer_join_engine_missing_column +03060_analyzer_regular_view_alias +03061_analyzer_alias_as_right_key_in_join +03064_analyzer_named_subqueries +03065_analyzer_cross_join_and_array_join +03066_analyzer_global_with_statement +03067_analyzer_complex_alias_join +03069_analyzer_with_alias_in_array_join +03070_analyzer_CTE_scalar_as_numbers +03071_fix_short_circuit_logic +03072_analyzer_missing_columns_from_subquery +03073_analyzer_alias_as_column_name +03074_analyzer_alias_column_in_view +03075_analyzer_subquery_alias +03077_analyzer_multi_scalar_subquery_aliases +03078_analyzer_multi_scalar_subquery_aliases +03079_analyzer_numeric_literals_as_column_names +03080_analyzer_prefer_column_name_to_alias__virtual_columns +03081_analyzer_agg_func_CTE +03082_analyzer_left_join_correct_column +03084_analyzer_join_column_alias +03085_analyzer_alias_column_group_by +03086_analyzer_window_func_part_of_group_by +03087_analyzer_subquery_with_alias +03089_analyzer_alias_replacement +03090_analyzer_multiple_using_statements +03093_analyzer_column_alias +03093_analyzer_miel_test +03093_reading_bug_with_parallel_replicas +03093_virtual_column_override_group_by +03093_with_fill_support_constant_expression +03094_analyzer_fiddle_multiif +03094_grouparraysorted_memory +03094_named_tuple_bug24607 +03094_transform_return_first +03095_join_filter_push_down_right_stream_filled +03095_merge_and_buffer_tables +03096_largest_triangle_3b_crash +03096_order_by_system_tables +03096_update_non_indexed_columns +03097_query_log_join_processes +03099_analyzer_multi_join +03100_analyzer_constants_in_multiif +03100_lwu_05_basics +03100_lwu_07_merge_patches +03100_lwu_08_multiple_blocks +03100_lwu_18_sequence +03100_lwu_19_nullable +03100_lwu_32_on_fly_filter +03100_lwu_33_add_column +03100_lwu_34_multistep_prewhere +03100_lwu_37_update_all_columns +03100_lwu_44_missing_default +03100_lwu_45_query_condition_cache +03100_lwu_47_apply_patches_after_drop_column +03100_lwu_49_update_skip_indexes +03101_analyzer_invalid_join_on +03102_prefer_column_name_to_alias +03104_create_view_join +03105_table_aliases_in_mv +03108_describe_union_all +03109_ast_too_big +03110_unicode_alias +03112_analyzer_not_found_column_in_block +03115_alias_exists_column +03116_analyzer_explicit_alias_as_column_name +03117_analyzer_same_column_name_as_func +03118_analyzer_multi_join_prewhere +03121_analyzer_filed_redefenition_in_subquery +03122_analyzer_collate_in_window_function +03125_analyzer_CTE_two_joins +03127_argMin_combinator_state +03128_merge_tree_index_lazy_load +03128_system_unload_primary_key +03129_cte_with_final +03129_low_cardinality_nullable_non_first_primary_key +03130_abs_in_key_condition_bug +03130_analyzer_array_join_prefer_column +03132_sqlancer_union_all +03141_wildcard_grants_on_tables +03142_alter_comment_parameterized_view +03142_skip_ANSI_in_UTF8_compute_width +03142_window_function_limit_by +03143_join_filter_push_down_filled_join_fix +03143_ttl_in_system_parts_columns_table +03144_aggregate_states_with_different_types +03144_alter_column_and_read +03145_unicode_quotes +03146_bug47862 +03146_tpc_ds_grouping +03147_datetime64_constant_index_analysis +03148_query_log_used_dictionaries +03149_analyzer_join_projection_name +03149_analyzer_join_projection_name_2 +03149_variant_pop_back_typo +03150_url_hash_non_constant_level +03151_analyzer_view_read_only_necessary_columns +03151_external_cross_join +03151_pmj_join_non_procssed_clash +03151_redundant_distinct_with_window +03152_dynamic_type_simple +03153_dynamic_type_empty +03155_datasketches_ubsan +03155_explain_current_transaction +03156_tuple_map_low_cardinality +03157_dynamic_type_json +03160_dynamic_type_agg +03161_decimal_binary_math +03161_ipv4_ipv6_equality +03164_analyzer_rewrite_aggregate_function_with_if +03164_analyzer_validate_tree_size +03164_create_as_default +03164_early_constant_folding_analyzer +03164_optimize_read_in_order_nullable +03165_distinct_with_window_func_crash +03165_order_by_duplicate +03165_string_functions_with_token_text_indexes +03166_mv_prewhere_duplicating_name_bug +03167_base64_url_functions_sh +03167_empty_tuple_concat +03167_fancy_quotes_off_by_one +03167_parametrized_view_with_cte +03168_cld2_tsan +03168_fuzz_multiIf_short_circuit +03169_cache_complex_dict_short_circuit_bug +03169_display_column_names_in_footer +03169_modify_column_data_loss +03169_optimize_injective_functions_inside_uniq_crash +03170_part_offset_as_table_column +03171_direct_dict_short_circuit_bug +03171_hashed_dictionary_short_circuit_bug_fix +03172_system_detached_tables_no_loop +03173_distinct_combinator_alignment +03174_merge_join_bug +03174_multiple_authentication_methods_show_create +03174_split_parts_ranges_into_intersecting_and_non_intersecting_final_and_read-in-order_bug +03175_sparse_and_skip_index +03195_group_concat_deserialization_fix +03196_max_intersections_arena_crash +03197_fix_parse_mysql_iso_date +03198_dictionary_validate_primary_key_type +03198_group_array_intersect +03198_h3_polygon_to_cells +03198_json_extract_more_types +03199_fix_auc_tie_handling +03199_has_lc_fixed_string +03199_join_with_materialized_column +03199_queries_with_new_analyzer +03200_memory_engine_alter_dynamic +03200_reverse_by_separator +03200_subcolumns_join_use_nulls +03201_analyzer_resolve_in_parent_scope +03201_sumIf_to_countIf_return_type +03202_enum_json_cast +03202_system_load_primary_key +03203_count_with_non_deterministic_function +03203_drop_detached_partition_all +03203_fill_missed_subcolumns +03203_multiif_and_where_2_conditions_old_analyzer_bug +03203_optimize_disjunctions_chain_to_in +03203_system_numbers_limit_and_offset_simple +03204_index_hint_fuzzer +03204_storage_join_optimize +03205_column_type_check +03205_hashing_empty_tuples +03205_json_cast_from_string +03205_json_syntax +03205_parallel_window_finctions_and_column_sparse_bug +03205_system_sync_replica_format +03206_is_null_constant_result_old_analyzer_bug +03208_datetime_cast_losing_precision +03208_groupArrayIntersect_serialization +03208_multiple_joins_with_storage_join +03208_numbers_total_rows_approx +03208_uniq_with_empty_tuple +03209_functions_json_msan_fuzzer_issue +03210_fix_single_value_data_assertion +03210_lag_lead_inframe_types +03210_nested_short_circuit_functions_bug +03210_variant_with_aggregate_function_type +03211_convert_outer_join_to_inner_join_anti_join +03213_array_element_msan +03213_denseRank_percentRank_alias +03214_join_on_tuple_comparison_elimination_bug +03214_json_typed_dynamic_path +03215_fix_get_index_in_tuple +03215_key_condition_bug +03215_partition_in_tuple +03215_toStartOfWeek_with_dateTime64_fix +03215_udf_with_union +03215_varian_as_common_type_integers +03215_view_with_recursive +03217_fliter_pushdown_no_keys +03217_primary_index_memory_leak +03221_key_condition_bug +03221_refreshable_matview_progress +03222_ignore_nulls_query_tree_elimination +03223_nested_json_in_shared_data_merges +03224_json_merges_new_type_in_shared_data +03224_nested_json_merges_new_type_in_shared_data +03224_trim_empty_string +03225_const_prewhere_non_ataptive +03227_dynamic_subcolumns_enumerate_streams +03227_test_sample_n +03228_dynamic_subcolumns_from_subquery +03228_join_to_rerange_right_table +03228_url_engine_response_headers +03228_variant_permutation_issue +03229_empty_tuple_in_array +03229_json_structure_comparison +03229_query_condition_cache_bug83262 +03229_query_condition_cache_cte_constant_folding +03229_query_condition_cache_in_operator +03229_query_condition_cache_recursive_cte +03230_anyHeavy_merge +03230_show_create_query_identifier_quoting_style +03230_subcolumns_mv +03230_system_projections +03231_prewhere_conditions_order +03232_json_uniq_group_by +03232_workload_create_and_drop +03235_groupArray_string_consistency +03236_create_query_ttl_where +03236_squashing_high_memory +03236_test_zero_field_decimal +03237_create_table_select_as_with_recursive +03237_get_subcolumn_low_cardinality_column +03237_max_map_state_decimal_serialization +03238_analyzer_unknown_function +03239_if_constant_folding +03240_array_element_or_null_for_map +03240_cte_in_subquery +03240_quantile_exact_weighted_interpolated +03241_view_block_structure +03242_view_block_structure +03243_array_join_lambda +03243_to_start_of_interval_aliases +03244_skip_index_in_final_query_part_of_pk +03244_skip_index_in_final_query_with_pk_rescan_basic +03244_skip_index_in_final_query_with_pk_rescan_extremes +03244_skip_index_in_final_query_with_pk_rescan_no_final_mark +03244_skip_index_in_final_query_with_pk_rescan_pk_subset +03245_ripemd160 +03245_views_and_filter_push_down_bug +03246_range_literal_replacement_works +03246_toStartOfInterval_date_timezone_bug +03247_json_extract_lc_nullable +03247_object_column_copy +03248_max_parts_to_move +03248_with_insert +03248_with_insert_with +03249_dynamic_alter_consistency +03250_ephemeral_comment +03250_json_group_by_sub_object_subcolumn +03252_fill_missed_arrays +03252_merge_tree_min_bytes_to_seek +03254_attach_part_order +03254_normalize_aggregate_states_with_named_tuple_args +03254_prewarm_mark_cache_columns +03254_project_lwd_respects_row_exists +03254_uniq_exact_two_level_negative_zero +03255_fix_sbstrings_logical_error +03257_json_escape_file_names +03257_reverse_sorting_key_simple +03258_multiple_array_joins +03258_old_analyzer_const_expr_bug +03258_quantile_exact_weighted_issue +03259_join_condition_executed_block_bug +03259_negate_key_overflow +03259_orc_date_out_of_range +03260_dynamic_low_cardinality_dict_bug +03261_any_respect_camelCase_aliases +03261_minmax_indices_by_default_table_copy +03261_sort_cursor_crash +03261_variant_permutation_bug +03262_analyzer_materialized_view_in_with_cte +03262_column_sizes_with_dynamic_structure +03262_const_adaptive_index_granularity +03262_system_functions_should_not_fill_query_log_functions +03263_analyzer_materialized_view_cte_nested +03266_with_fill_staleness_cases +03267_join_swap_bug +03268_empty_tuple_update +03268_system_parts_index_granularity +03269_partition_key_not_in_set +03270_empty_tuple_in_array_intersect +03270_fix_column_modifier_write_order +03271_decimal_monotonic_day_of_week +03272_bitmapTransform_error_counter +03272_json_to_json_cast_1 +03272_json_to_json_cast_3 +03272_json_to_json_cast_6 +03272_partition_pruning_monotonic_func_bug +03272_prewarm_mark_cache_add_column +03273_better_json_subcolumns_parsing +03273_dynamic_pretty_json_serialization +03273_group_by_in_order_still_used_when_group_by_key_doesnt_match_order_by_key +03273_primary_index_cache +03273_primary_index_cache_low_cardinality +03274_dynamic_column_sizes_vertical_merge +03274_grace_hash_max_joined_block_size_rows_bug +03274_philipzucker +03274_squashing_transform_sparse_bug +03274_with_fill_dup_sort_bug +03275_matview_with_union +03275_subcolumns_in_primary_key_bug +03276_functions_to_subcolumns_lc +03276_index_empty_part +03276_index_of_assume_sorted +03276_merge_tree_index_lazy_load +03276_parquet_output_compression_level +03277_logging_elapsed_ns +03278_dateTime64_in_dateTime64_bug +03279_not_empty_json +03280_dynamic_if_null +03281_dynamic_coalesce +03282_dynamic_in_functions_convert +03282_json_equal_comparison +03282_parallel_join_with_additional_filter +03283_json_binary_serialization_use_default_settings +03285_analyzer_extract_common_expr_bug +03285_analyzer_optimize_disjunctions +03286_collation_locale_with_modifier +03286_format_datetime_timezones +03286_parallel_replicas_cross_join_bug +03286_serialization_hint_system_columns +03287_format_datetime_mysqlfraction +03289_tuple_element_to_subcolumn +03290_final_collapsing +03290_final_replacing +03290_final_sample +03290_force_normal_projection +03290_mix_engine_and_query_settings +03290_partial_arrayROCAUC_and_arrayAUCPR +03290_pr_non_replicated_in_subquery +03292_format_tty_friendly +03298_analyzer_group_by_all_fix +03298_server_client_native_settings +03299_deep_nested_map_creation +03299_map_named_tuple +03299_pretty_squash +03300_nested_json_empty_keys +03301_is_ipv4_string +03301_subcolumns_in_mv +03302_any_enum_aggregation +03303_alias_inverse_order +03303_dynamic_in_not_xor +03304_pretty_fallback_to_vertical +03305_compressed_memory_eng_crash_reading_subcolumn +03306_optimize_table_force_keyword +03307_parallel_hash_max_joined_rows +03310_aggregate_projection_count_nullable +03311_subcolumns_in_default_and_materialized_expressions +03312_analyzer_unused_projection_fix +03312_sparse_column_tuple +03313_h3togeo_result_order +03314_analyzer_resolve_in_parent_scope_2 +03314_analyzer_resolve_in_parent_scope_3 +03314_analyzer_resolve_in_parent_scope_5 +03314_divide_decimal_short_circuit +03314_has_column_in_table_alias_column +03314_nullable_key_no_optimize_functions_to_subcolumns +03314_summing_merge_tree_final_not_found_column_in_block +03314_variant_rowbinary_file +03315_analyzer_correlated_subqueries +03315_array_join_scalar +03315_join_on_optimize_pass_alias +03315_quantile_bfloat16_ubsan +03317_index_hint_prewhere +03321_functions_to_subcolumns_skip_index +03321_inner_materialized_view_nested +03321_join_on_is_null_lowcardinality +03322_unused_interpolate_expressions +03323_bfloat16_least_supertype +03323_union_all_constants_bug +03325_alter_modify_projection_primary_key_column +03325_count_summing_merge_tree_order_by_tuple +03326_parallel_replicas_out_of_range +03326_toStartOfNanosecond_ubsan +03328_formatting_assignment_expression +03352_distinct_sorted_bug +03352_lazy_column_filter_by_uint8 +03354_translate_crap +03355_issue_32743 +03356_analyzer_unused_scalar_subquery +03357_block_structure_union_step +03358_block_structure_match +03359_point_in_polygon_index +03363_constant_nullable_key +03363_function_keccak256 +03364_pretty_json_bool +03365_dynamic_column_datetime +03365_finish_sorting_crash +03365_time64_casts +03365_time_implicit_conversion +03365_time_time64_aggregate_functions +03365_time_time64_best_effort_parsing +03365_time_to_time64_conv_bug +03366_bfloat16_sorting +03366_with_fill_dag +03367_bfloat16_tuple_final +03368_bfloat16_merge_join +03369_bfloat16_map +03370_join_identifiers +03371_bfloat16_special_values +03371_nullable_tuple_string_comparison +03374_date_trunc_with_negatives +03375_bloom_filter_array_equals +03375_bool_partition +03380_accurate_cast_or_null_qbit +03389_regexp_rewrite_nullable_group_by +03392_crash_group_by_use_nulls +03393_smallest_index_floating_point +03394_fix_to_start_of_interval_for_zero_origin_argument +03395_global_join_supported_kind +03399_divide_zero_or_null +03399_mapContains_functions +03399_sparse_grams +03400_datetime64_key_condition_with_number +03401_remote_bool +03402_materialized_tuple_element +03402_zero_streams_after_max_streams_to_max_threads_ratio +03403_read_in_order_streams_memory_usage +03404_bfloat16_insert_values +03404_dynamic_in_interval_bug +03404_geotoh3_input_order +03404_ubsan_distinct_join_const_column +03405_bool_array_to_fixed_strings +03406_reservoir_sample_self_merging +03407_parse_date_time_best_effort_unix_timestamp_with_fraction +03411_analyzer_scalar_correlated_subquery +03411_iceberg_bucket +03411_summing_merge_tree_dynamic_values +03413_analyzer_correlated_subqueries_bug_2 +03414_formatDateTime_compound_formatter_after_varsize_formatter +03415_dont_highlight_probable_hashes +03443_index_match_alternatives +03444_lm_block_mismatch +03447_float_nan_order +03447_function_reverse_for_tuple +03447_grouping_sets_analyzer_const_columns +03447_window_functions_distinct +03448_topk_merging +03449_window_cannot_find_column +03450_parameterized_view_forward +03451_parameterized_views_without_alias +03454_parameterized_view_constant_identifier +03454_parameterized_views_null +03458_numeric_indexed_vector_operations_i8f64 +03458_numeric_indexed_vector_operations_u16f64 +03458_numeric_indexed_vector_operations_u32f32 +03458_numeric_indexed_vector_operations_u32f64 +03458_numeric_indexed_vector_operations_u32i16 +03458_numeric_indexed_vector_operations_u32i32 +03458_numeric_indexed_vector_operations_u32i64 +03458_numeric_indexed_vector_operations_u32i8 +03458_numeric_indexed_vector_operations_u32u16 +03458_numeric_indexed_vector_operations_u32u32 +03458_numeric_indexed_vector_operations_u32u64 +03458_numeric_indexed_vector_operations_u32u8 +03459_join_cannot_add_column +03459_numeric_indexed_vector_decode +03459_socket_asynchronous_metrics +03460_numeric_indexed_vector_to_value_map +03461_numeric_indexed_vector_chain +03461_projection_index_multi_part +03462_numeric_indexed_vector_serialization +03509_stripe_log_compatible_types +03511_formatDateTime_e_space_padding +03513_filter_push_down_rand_bug +03513_resize_pipeline_after_totals +03515_array_join_different_sizes +03516_comparison_pk_bug +03516_int_exp2_join +03517_logical_join_predicate_push_down_with_pre_expression_bug +03518_left_to_cross_incorrect +03519_fulter_push_down_duplicate_column_name_bug +03519_left_to_cross_incorrect +03519_merge_tree_part_info_coverage +03520_left_to_cross_incorrect +03521_bitNot_String_NUL_terminated +03521_system_unicode +03521_tuple_of_dynamic_with_string_comparison +03522_alter_modify_column_and_materialize_projection +03524_nullable_extremes +03525_json_extract_datetime64_from_numbers +03525_json_infer_array_of_dynamic_from_array_of_different_types +03532_divideOrNull_jit_crash +03532_dynamic_column_inside_map_rollback +03538_analyzer_correlated_query_collect_columns_fix +03538_analyzer_scalar_correlated_subquery_fix +03538_higher_order_functions_null_filter +03545_array_join_index_set_bug +03545_map_contains_bloom_index_bug +03545_union_allow_column_with_no_common_type +03547_analyzer_correlated_subqueries +03548_optimize_syntax_fuse_functions_clash +03549_conv_function +03549_system_dimensional_metrics +03550_variant_extend_union +03551_cast_decimal_to_float +03555_inconsistent_formatting_ttl +03560_low_cardinality_keys_filter +03562_json_date_as_integer +03563_coarser_minmax_indexes_first +03565_clickhouse_smaller_indexes_first +03567_json_extract_case_insensitive_edge_cases +03568_ddsketch_merge +03568_json_extract_case_insensitive +03571_join_inequality_constants +03572_planner_merge_filter_into_join_bug +03573_concurrent_hash_scatter_bug +03573_planner_merge_filter_into_join_bug_2 +03574_analyzer_merge_filter_into_join_bug +03574_parallel_replicas_last_right_join +03575_analyzer_merge_filter_into_join_bug_2 +03577_assert_on_estimated_block_size_bytes +03577_hash_output_format +03577_ub_max_column_in_block_size_bytes +03577_vairant_lazy_materialization_bug +03578_parallel_replicas_formatting_tuple +03579_system_columns_column_alias +03579_zero_copy_aggregating_final_anyLast +03580_external_merge_sort_with_lazy_columns +03580_join_runtime_filter_column_type +03580_join_runtime_filter_pushdown +03581_bool_literal_column_name +03581_parallel_replicas_read_empty_ranges +03582_initcap_fixedstring +03582_normalize_utf8_empty +03592_dictionary_columns_trailing_comma +03593_any_join_swap_tables +03593_backup_with_broken_projection +03594_alias_subcolumns +03594_coalescing_merge_tree_segfault +03594_funcs_on_empty_arguments +03594_json_extract_decimal_precision +03594_like_perfect_affix_rewrite_pass +03594_push_more_filters_down_joins +03595_alter_if_exists_mixed_commands +03595_alter_if_exists_runtime_check +03595_funcs_on_zero +03596_parquet_prewhere_page_skip_bug +03598_json_enum_default_value_in_typed_path +03599_bad_date_and_datetimes_inference +03600_analyzer_setting_bool +03600_replace_fixed_string_bug +03601_histogram_quantile +03601_inconsistent_table_names +03601_insert_squashing_remove_const +03601_json_from_string_accurate_cast_or_null +03601_replace_regex_fixedstring_empty_needle +03602_query_system_tables_definer +03604_and_join_use_nulls_bug_83977 +03604_functions_to_subcolumns_outer_join +03604_join_reorder_pinned_bug +03604_key_condition_set_tuple_bug +03604_plan_step_description_limit +03604_to_date_casts +03606_nullable_json_group_by +03611_cte_deterministic +03611_pr_global_join +03611_uniqExact_bug +03612_explain_indexes_bugs +03613_empty_tuple_permute_with_limit +03620_json_advanced_shared_data_seek_bug +03622_generic_aggregate_functions__state_compatibility +03623_datetime64_preepoch_fractional_precision +03623_lazy_materialization_array_sizes_bug +03623_parquet_bool +03624_parquet_row_number +03624_pr_lefl_right_joins_chain +03624_resource_exhaustion_window_function +03625_case_without_condition_non_constant_branches +03625_prewhere-and-default-bug +03625_upper_lower_utf8_different_number_of_code_points +03626_case_function_with_dynamic_argument +03627_non_constant_replacement_in_replace_regexp +03628_named_tuple_element_in_order_by_key +03628_parse_date_time_short_circuit +03629_starts_endswith_caseinsensitive +03630_hash_join_max_block_size +03630_join_blocks_with_different_constness +03630_parquet_bool_bug +03631_array_of_empty_tuples +03631_select_replace_comprehensive +03632_default_minmax_indices_alter +03632_join_logical_assert_85403 +03633_negative_limit_offset +03633_set_index_bulk_filtering +03635_in_function_different_types_many_columns +03636_empty_projection_block +03636_index_analysis_with_session_tz +03638_merge_max_dynamic_subcolumns_in_compact_part +03638_merge_max_dynamic_subcolumns_in_wide_part +03639_hash_of_dynamic_column +03639_hash_of_json_column +03640_skip_indexes_with_or_and_not +03640_variant_array_null_map_subcolumn +03641_analyzer_issue_85834 +03641_json_array_of_float_and_bool +03642_column_ttl_sparse +03643_paste_join_disable_filter_pushdown +03643_system_instrumentation_no_suspicious_lowcardinality +03644_explain_indices +03644_join_order_mixed_comma_and_left +03644_min_level_for_wide_part +03644_rows_before_aggregation_in_order +03651_positional_argument_agg_projection +03652_coalescing_merge_tree_fix_empty_tuple +03652_join_using_legacy_step +03652_union_columns_2 +03653_fractional_limit_offset +03654_case_non_constant_null +03654_grouping_sets_any_min_max +03656_nan_comparison +03657_gby_overflow_any_sparse +03657_rollup_constant +03660_udf_subquery +03664_parameterized_view_restart +03666_count_matches_complexity +03667_pr_join_with_cross_join_on_left +03671_dict_in_subquery_in_index_analysis_context_expired +03672_columns_same_as_subcolumns +03672_nested_array_nested_tuple +03699_reverse_utf8 +03700_vertical_format_pretty_print_json +03701_distinct_but_no_group_by_projection_table_use_check +03701_limit_by_in_order +03702_encode_decode_memory_usage +03702_json_datetime_format_settings +03702_optimize_inverse_dictionary_lookup_composite_and_layouts +03703_function_dict_get_keys_large +03703_statistics_low_cardinality +03704_default_empty_order_by +03704_fractional_limit_with_ties +03704_function_dict_get_keys_cache_type +03705_count_if_asterisk +03705_fix_compression_T64_unaligned +03705_function_dict_get_keys_multiple_dict_and_no_caching +03707_autopr_unsupported_cases +03707_set_index_bad_get_null_bug +03707_statistics_cache +03708_analyzer_convert_any_outer_to_inner_2 +03708_exact_rows_before_limit_in +03708_flush_async_insert_queue_for_table +03708_join_or_to_right_any_bug +03708_low_cardinality_aggregate_state_compatibility +03708_statistics_estimator_cast_type +03709_anti_join_runtime_filters +03709_coalescing_final +03709_final_prewhere_special_columns +03709_remove_unused_columns_from_prewhere +03710_argAndMinMax +03710_midpoint_jit +03710_pr_insert_into_mv_with_join +03710_pr_join_with_mv +03711_top_k_by_skip_index_negative +03712_json_advanced_shared_data_bug +03713_group_by_injective_function_old_analyzer +03714_base32_base58_short_string +03714_empty_tuple_reverse_function +03714_queries_escaping_1 +03714_queries_escaping_2 +03715_empty_tuple_functions_conversion +03716_bson_each_row_empty_tuple_column +03716_join_duplicate_columns_89411 +03716_join_right_side_sorting +03717_system_unicode_enums +03719_generic_hash_over_constant_and_non_constant +03719_ntile_no_partition_by_check +03720_datetime64_bad_inference +03720_insert_mem_no_self_deduplication +03721_check_natural_sort_key +03721_insert_replicated_array_nested_sizes_check +03721_join_residual_condition_bug_88635 +03721_right_join_logical_step +03721_statistics_alter_type_bug +03722_iceberg_hash_bug +03722_json_compact_part_substreams_cache_bug +03722_normal_projection_key_columns +03723_incorrect_implicit_projection +03724_to_date_time_or_null_negative_arg_bug +03725_empty_tuple_some_limit_with_ties_distinct +03725_json_dynamic_subcolumn_prewhere_zero_index_granularity_bytes +03725_variant_element_null_map_subcolumn_prewhere_zero_index_granularity_bytes +03726_array_union_with_dynamic_argument +03727_block_structure_mismatch_after_filter_push_down +03727_ipv4_parsing_bug +03727_named_tuples_pretty_format +03727_prewhere_intermediate_columns +03727_tolowcardinality_nullable_cast +03728_analyzer_identifier_resolution_join +03728_explain_column_structure +03731_null_parts_in_storage_snapshot_with_only_analyze +03732_json_duplicated_path_in_dynamic_paths_and_shared_data_bug +03732_json_duplicated_path_in_dynamic_paths_and_shared_data_compact_part_bug +03732_toweek_partition_pruning +03733_anti_join_runtime_filter_3 +03733_base58_decode_bug +03733_summing_merge_tree_nested_low_cardinality +03740_alter_modify_query_dict_name_in_cse +03741_insert_select_subquery_from_file +03741_subcolumns_of_materialized_columns_in_mutation +03742_array_join_empty_tuple +03742_lazy_materialization_of_array_after_alter_add_column +03742_test_flattened_crash +03743_fix_estimator_crash +03748_tuple_of_sparse_elements_bug +03749_arrayROCAUC_variant_type +03749_cross_join_use_nulls_matcher +03749_implicit_index_ephemeral_alias +03749_materialized_view_not_supports_parallel_write +03750_json_in_tuple_element +03751_if_uint64_int32_type_mismatch +03751_join_empty_string_nullable +03751_summing_coalescing_merge_tree_sparse_columns_in_header +03752_fractional_limit_offset_small_blocks +03752_join_part +03753_anti_join_runtime_filter_4 +03753_join_runtime_filter_dynamically_disable +03753_log_engine_shared_prefixes_bug +03754_fractional_limit_offset_multiple_streams +03754_h3_polygon_to_cells_const +03754_point_in_polygon_primary_key_index +03755_concurrent_hash_join_dispatch_bug +03755_final_prewhere_duplicate_columns +03755_hive_partitioning_used_to_break_insert +03756_sparse_serialization_nullable_in_tuple +03757_ast_not_formatting +03760_consistent-in-formatting +03760_join_reorder_outer_inner_nulls +03761_convert_outer_to_inner_join_keeps_header +03761_count_distinct_optimization_window +03761_inconsistent_ast_formatting +03761_lazy_materialization_split_expr +03761_log_with_string_size +03762_count_distinct_optimization_multiple_columns +03762_parquet_cast_to_json +03763_column_conflicting_with_subcolumn +03763_no_new +03764_empty_tuple_permutation_bug +03764_join_nothing_type_column_crash +03766_subcolumns_resolution_in_aliases +03767_implicit_minmax_index_dynamic_variant +03768_rename_alias_column_implicit_index +03769_implicit_index_alias_simple_reference +03770_long_index_name_hash +03771_tokens_crash +03773_join_on_formatting +03773_json_with_typed_path_skip +03773_key_condition_not_materialize_not_cast +03773_lightweight_update_index_subquery +03773_nullable_sparse_join +03773_parquet_roundtrip_bug +03774_json_skip_regexp_partial_match +03774_ttl_group_by_lowcardinality +03775_join_on_disk_max_files_to_merge +03776_subcolumns_in_partition_by +03776_text_index_covered_sparse_grams +03777_analyzer_unused_columns_removal_correlated_subquery +03777_join_precalculate_keys +03777_join_runtime_filter_duplicate_columns +03777_join_runtime_filter_prepared_right_table +03777_join_runtime_filter_unsupported_type +03777_join_runtime_filter_with_consts +03777_join_runtime_filter_with_totals +03777_merge_engine_with_aliases_bug +03777_runtime_filter_and_merge_table +03777_top_k_dynamic_filtering_respect_header_bug +03778_read_subcolumns_from_projection +03779_float_nan_key_condition +03779_lexer_pointer_overflow_ubsan +03780_has_all_tokens_empty_array +03780_json_typed_paths_with_same_prefix +03781_json_max_dynamic_subcolumns_control_on_parsing +03781_merge_hostname_remote +03781_split_join_filter_bug +03782_parse_describe_select_without_parentheses_around_select +03784_correlated_subquery_from_storage +03784_materialize_index_subcolumns +03784_msan_token_iterator +03785_rebuild_projection_with_part_offset +03786_empty_tuple_map_array +03787_pr_group_by_read_mode_error +03787_storage_join_type_conversion_2 +03788_hash_join_with_empty_right_side_skip_left_side +03788_header_after_outer_to_semi_or_anti_conversion +03788_parquet_writer_bad_bool +03788_semi_join_filter_pushdown_bug +03788_statistics_part_pruning_nan +03788_table_function_results_race +03789_right_join_column_replicated +03789_skip_index_bad_utf8 +03790_uniqTheta_error +03791_decimal_string_zero +03791_function_to_subcolumns_optimization_on_subcolumns +03791_system_parts_files_column +03792_jit_date32 +03792_jit_time64 +03792_sparse_offsets_in_substreams_cache_compact_part +03793_logical_expressions_optimizer_group_by_use_nulls +03793_with_fill_staleness_long_jump +03794_read_in_order_virtual_row_and_sparse_primary_key_msan +03795_hash_output_format_blocks +03797_set_index_multiple_conditions_same_expression +03798_nullable_primary_key_logical_error +03799_any_left_join_isnotnull_crash +03799_cross_to_inner_join_view_with_join +03799_parallel_right_join_flag_per_row +03799_row_order_optimize_duplicate_sorting_columns +03800_string_size_stream_in_nested_types +03801_domain_empty_string_msan +03802_ephemeral_enum_type +03802_pr_join_non_mt_table +03804_insert_select_parentheses +03805_array_filter_nullable_lambda +03805_merge_table_filter_push_down_uninitialized_plan +03807_top_k_limit_zero +03808_correlated_subquery_with_totals +03808_inconsistent_formatting_index_ttl_alias +03808_with_duplicate_alias_nested_scopes +03809_time64_not_monotonic +03810_pr_aggr_in_order_read_mode +03811_contingency_window_functions_nested_combinators_merge +03811_inconsistent_formatting_ttl_where_alias +03811_sparse_column_aggregation_with_sum +03812_column_function_prewhere_filter +03812_reading_subcolumns_of_aliases +03815_array_of_json_in_tuple_element +03815_tuple_element_with_json +03821_direct_join_empty_table +03821_json_skip_path_fix +03821_materialized_cte_with_sets +03821_uniq_speedup_boundaries +03822_alias_column_skip_index_no_merge +03822_insert_null_into_non_nullable_enum +03822_join_reorder_fix_expression_sources +03822_lttb_date32_negative +03822_lwu_patch_type_mismatch_crash +03822_window_function_large_preceding_offset +03823_lwu_patch_type_mismatch_dataloss +03823_prefetched_read_pool_priority_assertion +03823_skip_index_disjunction_oob +03824_index_reader_const_granularity_last_mark +03824_join_swap_output_header_mismatch +03825_array_with_constant_fixed_string +03825_bitxor_if_mixed_types +03825_join_right_sorting_empty_maps +03825_xxh3_128_hash_function +03826_array_join_in_bloom_filter +03827_cross_join_no_right_columns +03829_distance_transposed_pass_nullable_nothing +03832_array_join_filter_push_down_cross_join +03832_log_engine_alias_subcolumn +03833_server_ast_fuzzer +03835_join_transitive_predicates_outer +03835_todate_monotonicity_boundary +03836_array_join_in_filter_partial_evaluation +03902_format_datetime_fractional_msan +03902_jit_cast_to_bool +03904_fix_empty_statistics_load +03905_grouping_sets_use_nulls_lowcardinality_in_tuple +03905_select_replace_where_same_column +03905_set_partition_pruning_key_condition +03905_skip_index_set_null +03905_window_function_constant_in_group_by +03906_group_by_lowcardinality_parallel_hash_join +03908_lazy_materialization_replacing_merge_tree +03909_union_all_remote_predicate_pushdown +03910_limit_always_read_till_end_with_constant_false_having +03910_right_join_storage_join_qualify_type_mismatch +03911_ephemeral_virtual_column_old_analyzer +03912_set_index_dynamic_type +03912_tuple_comparison_group_by_use_nulls +03913_jemalloc_cache_arena_metrics +03913_minmax_statistics_nan_ubsan_error +03913_text_index_preprocessor_expression +03914_parquet_v3_prewhere_non_bool_filter +03916_coalescing_merge_tree_const_column_from_index +03916_contingency_state_variant_serialization_match +03916_except_intersect_all_multiset +03916_jit_datetime_to_datetime64_scale +03916_lazy_materialization_union_all +03916_window_functions_group_by_use_nulls +03918_allow_nullable_tuple_in_extracted_subcolumns_not_changeable +03918_array_element_negate_int_min +03918_if_transform_strings_to_enum_nullable +03918_insert_squashing_const_different_values +03918_replace_table_clone_as_verbose_result +03919_json_lazy_type_hints_incompatible +03919_variant_adaptor_lowcardinality +03921_internal_functions_have_internal_category +03921_merge_tree_settings_readonly +03923_union_all_cte_skip_index_prewhere +03924_constant_granularity_last_mark +03924_duplicated_key_with_literal_and_nested_object_in_typed_paths_in_json +03924_sorting_step_stats +03925_exists_with_mutations_execute_on_initator +03925_if_nullable_const_branch +03925_json_shared_data_buckets_missing_stream_bug +03925_sparse_values_in_substreams_cache_bug +03925_tuple_comparison_nothing_type +03925_variant_adaptor_const_lowcardinality +03926_convert_to_full_recursive_tuple_sparse +03926_group_by_use_nulls_constant_type +03926_semi_join_swap_tables +03927_correlated_subquery_table_function_context +03927_formatting_consistency_tuple_on_clause +03927_join_use_nulls_subcolumn +03927_json_typed_path_tuple_element +03927_nested_columns_duplicate_identifier +03927_row_filter_in_read_in_order_optimization +03928_deferred_filters_after_final_in_explain +03928_full_join_swap_tables +03928_json_advanced_shared_data_bug +03928_materialized_cte_index +03928_materialized_cte_index_2 +03928_merge_lambda_alias_columns +03976_geometry_functions_accept_subtypes +03977_pr_expected_join +03979_datetime64_preepoch_partition_pruning +03980_datetime64_preepoch_partition_pruning_explain +03984_in_single_column_reference +03985_parenthesized_from_join +03987_concat_variant_low_cardinality +03988_concat_with_separator_format_variant_low_cardinality +03988_grace_hash_join_leftover_blocks +03988_lazy_final_replacing_merge_tree +03988_map_contains_key_like_tokenbf +03988_map_with_buckets_create_and_write +03989_codec_ignore_nulls_formatting +03990_map_subcolumn_skip_index +03991_json_single_bucket_empty_shared_data +03992_join_using_remote_table_function_with_alias +03992_map_subcolumns_small_memory +03993_join_no_overlapping_columns_85622 +04001_logical_error_with_cube +04002_sign_jit_miscompilation +04003_interpolate_element_with_alias_formatting +04004_finalize_projection_parts_synchronously +04004_insert_compound_select_formatting +04004_kql_array_sort_const +04005_case_with_expression_nullable_nothing +04005_correlated_column_in_lambda +04005_window_single_line_formatting +04005_with_fill_limit_by +04006_convert_join_to_in_dummy_column +04006_correlated_columns_collector_scope +04006_not_subquery_not_like_formatting +04008_count_distinct_optimization_with_qualify +04008_log_engine_nested_subcolumn_cache +04010_column_replicated_handling_in_partial_sorting_transform +04010_explain_actions_pretty_and_verbose +04010_filter_pushdown_non_boolean_output +04010_null_safe_cmp_dynamic_const +04012_versioned_collapsing_final_filter +04013_loop_function_output_columns +04014_json_compact_each_row_null_string +04016_exists_group_by_resolution +04017_column_alias_set_operations +04021_join_on_false_chunk_rows +04024_jit_multiif_mixed_types +04024_prewhere_parquet_keycondition +04026_prewhere_qualify_text_index +04027_final_prewhere_constant_where_remove_unused_columns +04028_recursive_cte_remote_view_segfault +04028_squashing_with_strict_bounds_bytes_underestimation +04029_read_in_order_expression_virtual_row +04029_with_fill_staleness_cross_chunk +04030_array_fold_memory_peak_guard +04030_text_index_condition_logical_error +04032_json_subcolumn_in_projection_sorting_key +04033_exists_with_limit_offset +04033_top_k_dynamic_filter_nullable_string +04034_any_left_join_in_subquery_default_value +04035_toDaysInMonth +04037_map_key_subcolumn_tuple_value_mergetree +04038_check_table_sparse_tuple_dynamic +04039_explain_union_settings_formatting_roundtrip +04039_text_index_direct_read_select_alias +04039_text_index_map_values_subcolumn +04040_clear_column_with_projection_index +04040_window_funnel_strict_deduplication +04041_format_create_as_select_with_output_options +04042_materialized_cte_union +04042_text_index_prewhere_alias_select +04043_text_index_in_with_preprocessor +04044_recursive_cte_remote_view_actual_recursion +04045_ambiguous_column_alias_values +04045_values_insert_trailing_comment +04046_format_alter_modify_query_with_settings +04046_midpoint_mixed_types +04046_sipHash128Keyed_map_with_empty_arrays +04047_arrow_uuid_nested_reader +04049_aggregate_function_numeric_indexed_vector_self_merge +04049_arrayRemove_nullable_tuple +04049_cuturlparameter_pattern_matching +04049_remote_subquery_in_unresolved_table_function_argument +04049_statistics_enum_invalid_value +04049_values_clause_standard_sql +04049_with_func_alias_in_view +04050_accurateCastOrDefault_const +04050_explain_ast_insert_with_output_options +04050_lc_nullable_query_parameters +04050_prune_array_join_numeric_index +04050_time64_cast_uint64_no_wrap +04050_truncate_order_by_after_group_by_keys +04051_array_first_last_variant +04051_clear_column_rebuild_dependencies +04051_runtime_filter_variant_type +04052_kql_array_sort_row_independence +04053_limit_push_down_union +04053_merge_tree_analyze_indexes_union_all +04053_render_tab_as_spaces_regression +04054_block_mismatch +04054_sample_offset_formatting_ambiguity +04054_transform_const_default_column +04056_highlight_null_terminator +04057_explain_except_ast_formatting +04058_positive_modulo_ubsan +04058_statistics_bad_cast +04061_array_dot_product_empty_arrays +04062_join_order_optimizer_outer_join +04064_alias_datetime_timezone_cast +04064_format_cte_column_aliases +04064_join_on_or_is_not_distinct_from +04064_join_use_nulls_apply_lambda_multiple_joins +04064_parameterized_view_scalar_subquery +04064_planner_duplicate_column_identifier +04064_recursive_cte_multiple_subqueries +04068_geo_wkt_wkb_uppercase_aliases +04068_lazy_materialization_map_nested_alter_add +04068_window_function_type_mismatch_with_rollup_and_group_by_use_nulls +04070_array_exists_to_has_null +04070_decimal_min_max_jit_signedness +04070_join_using_type_coercion_round +04070_pad_string_asan_overflow +04070_subnormal_float_literal +04071_jit_decimal_aggregates +04072_jit_decimal_expressions +04073_float_itoa_fast_path +04074_map_dynamic_substreams_cache +04074_where_large_int_boolean_predicate +04075_analyzer_inline_view_subcolumns +04075_float_to_string_format +04076_array_symmetric_difference_nullable_tuple +04076_materialized_cte_dependency_ordering +04076_tuple_json_dotted_subcolumn_bug +04077_formatdatetime_w_is_a_variable_length_formatter +04077_lwd_qualified_column +04077_vector_similarity_cosine_msan +04086_03711_positive_modulo_tuple +04093_negative_time_datetime_comparison +04094_sz_find_null_needle_oob +04094_text_index_like_large_postings +04096_text_index_json_subcolumn +04101_crosstab_window_state_serialization_pool +04102_text_index_alias_direct_read +04103_cast_string_to_date_time_mode_nullable +04103_full_sorting_merge_left_join_nullable_json +04103_plain_rewritable_get_last_modified_bug +04104_functions_context_lifetime_in_defaults +04108_replicated_and_non_replicated_columns_merge +04113_has_subsequence_empty_haystack +04132_column_sparse_ops +04143_if_decimal_int_literal_jit +04143_ub_parsing_datetime +04145_url_glob_parallelize_output +04146_obfuscate_query_known_identifiers +04146_short_circuit_evaluation_for_nulls +04146_spilling_hash_join_low_threshold +04146_to_hour_monotonicity_dt64_timezone +04200_fix_time64_scale_conversion +04201_to_millisecond_negative_datetime64 +04202_array_dot_product_combine_overflow +04202_parsedatetime_or_null_zero_post_instruction_errors +04202_projection_prewhere_materialize +04202_variant_basic_mode_only_nulls +04207_cross_join_starts_with_memory +04216_materialized_view_reused_target_name_dependency +04217_array_concat_json_extract_keys_index +04217_arrayjoin_set_index_lowcardinality_70058 +04217_create_table_as_with_settings +04221_dictgetordefault_arrayjoin_80703 +04222_prewhere_final_replacing_86268 +04223_text_index_direct_read_like_90802 +04225_row_policy_groupby_final_91946 +04227_prewhere_parameterized_view_join_102074 +04228_row_policy_view_join_analyzer_102651 +4060_row_policy_projection_not_found_column diff --git a/tests/clickhouse-test-runner/vitest.config.ts b/tests/clickhouse-test-runner/vitest.config.ts new file mode 100644 index 000000000..422de8509 --- /dev/null +++ b/tests/clickhouse-test-runner/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['__tests__/**/*.test.ts'], + }, +}) diff --git a/tests/e2e/skills/check.js b/tests/e2e/skills/check.js index b0531d142..7da08b67c 100644 --- a/tests/e2e/skills/check.js +++ b/tests/e2e/skills/check.js @@ -1,5 +1,22 @@ 'use strict' +// E2E packaging check for shipped AI-agent skills. +// +// Source of truth: the repo-root `skills/` directory. Every skill that lives +// there is shipped via `@clickhouse/client` (its `prepack` copies the entire +// `skills/` tree into the package), so this script discovers skills from the +// source directory and asserts that each one is: +// +// 1. declared in `agents.skills` of the installed @clickhouse/client +// package.json (with matching `path`), +// 2. present at the declared path inside the installed package and contains +// a `SKILL.md`, +// 3. symlinked into `.claude/skills/` by skills-npm. +// +// It also asserts that `agents.skills` does not declare any skill that is +// missing from the source `skills/` directory, and that `@clickhouse/client-web` +// ships no skills. + const assert = require('assert') const fs = require('fs') const path = require('path') @@ -16,36 +33,77 @@ function check(description, fn) { } const nm = path.join(__dirname, 'node_modules') +const repoRoot = path.resolve(__dirname, '..', '..', '..') +const skillsSrcDir = path.join(repoRoot, 'skills') + +// Discover skills from the source-of-truth `skills/` directory. +assert.ok( + fs.existsSync(skillsSrcDir), + `source-of-truth skills directory not found at ${skillsSrcDir}; this script is meant to run from tests/e2e/skills inside the clickhouse-js repo`, +) +const expectedSkills = fs + .readdirSync(skillsSrcDir, { withFileTypes: true }) + .filter((d) => d.isDirectory() && fs.existsSync(path.join(skillsSrcDir, d.name, 'SKILL.md'))) + .map((d) => d.name) + .sort() + +check('repo skills/ directory contains at least one skill', () => { + assert.ok( + expectedSkills.length > 0, + `expected at least one skill under ${skillsSrcDir}`, + ) +}) -// @clickhouse/client (Node.js) +// @clickhouse/client (Node.js) — ships every skill from the repo `skills/` tree. const nodeRoot = path.join(nm, '@clickhouse', 'client') -const nodeSkillRoot = path.join( - nodeRoot, - 'skills', - 'clickhouse-js-node-troubleshooting', +const nodePkg = JSON.parse( + fs.readFileSync(path.join(nodeRoot, 'package.json'), 'utf8'), ) +const declaredSkills = Array.isArray(nodePkg.agents?.skills) + ? nodePkg.agents.skills + : [] check('@clickhouse/client skills dir exists', () => assert.ok(fs.existsSync(path.join(nodeRoot, 'skills'))), ) -check('@clickhouse/client SKILL.md exists', () => - assert.ok(fs.existsSync(path.join(nodeSkillRoot, 'SKILL.md'))), +check( + '@clickhouse/client agents.skills declares every skill from skills/', + () => { + const declaredNames = declaredSkills.map((s) => s.name).sort() + assert.deepStrictEqual( + declaredNames, + expectedSkills, + `agents.skills (${JSON.stringify(declaredNames)}) must match the skills found under skills/ (${JSON.stringify(expectedSkills)})`, + ) + }, ) -check('@clickhouse/client package.json declares agents.skills', () => { - const pkg = JSON.parse( - fs.readFileSync(path.join(nodeRoot, 'package.json'), 'utf8'), - ) - assert.ok( - Array.isArray(pkg.agents?.skills), - 'agents.skills should be an array', - ) - assert.ok( - pkg.agents.skills.some( - (s) => s.name === 'clickhouse-js-node-troubleshooting', - ), - 'agents.skills should include clickhouse-js-node-troubleshooting', + +for (const skill of declaredSkills) { + check( + `@clickhouse/client agents.skills entry "${skill.name}" has a valid path`, + () => { + assert.ok( + typeof skill.path === 'string' && skill.path.length > 0, + `agents.skills entry for ${skill.name} must declare a "path"`, + ) + const expectedPath = `./skills/${skill.name}` + assert.strictEqual( + skill.path, + expectedPath, + `agents.skills entry for ${skill.name} should declare path "${expectedPath}"`, + ) + const resolved = path.join(nodeRoot, skill.path) + assert.ok( + fs.existsSync(resolved), + `declared skill path does not exist in installed package: ${skill.path}`, + ) + assert.ok( + fs.existsSync(path.join(resolved, 'SKILL.md')), + `declared skill at ${skill.path} should contain SKILL.md`, + ) + }, ) -}) +} // @clickhouse/client-web — no skills yet; verify the package installed cleanly and does not ship skills check('@clickhouse/client-web installs without skills dir', () => { @@ -60,39 +118,47 @@ check('@clickhouse/client-web installs without skills dir', () => { ) }) +// skills-npm — symlinks each declared skill under `.claude/skills/`. +const skillsLinkDir = path.join(__dirname, '.claude', 'skills') + check('skills-npm creates a skills/ directory', () => { assert.ok( - fs.existsSync(path.join(__dirname, '.claude', 'skills')), + fs.existsSync(skillsLinkDir), 'skills/ directory should be created by skills-npm', ) }) -check('skills-npm symlinks clickhouse-js-node-troubleshooting', () => { - const skillsDir = path.join(__dirname, '.claude', 'skills') - const entries = fs.readdirSync(skillsDir) - const npmLinks = entries.filter((e) => e.startsWith('npm-')) - assert.ok( - npmLinks.length > 0, - 'skills/ should contain at least one npm-* symlink', - ) +const npmLinks = () => + fs.existsSync(skillsLinkDir) + ? fs.readdirSync(skillsLinkDir).filter((e) => e.startsWith('npm-')) + : [] - const troubleshootingLink = npmLinks.find((e) => - e.includes('clickhouse-js-node-troubleshooting'), - ) +check('skills-npm creates at least one npm-* symlink', () => { + const links = npmLinks() assert.ok( - troubleshootingLink, - `skills/ should contain a symlink for clickhouse-js-node-troubleshooting, found: [${npmLinks.join(', ')}]`, - ) - - const linkPath = path.join(skillsDir, troubleshootingLink) - assert.ok( - fs.lstatSync(linkPath).isSymbolicLink(), - `${troubleshootingLink} should be a symlink`, - ) - assert.ok( - fs.existsSync(path.join(linkPath, 'SKILL.md')), - 'symlinked skill should contain SKILL.md', + links.length > 0, + 'skills/ should contain at least one npm-* symlink', ) }) +for (const skill of declaredSkills) { + check(`skills-npm symlinks ${skill.name}`, () => { + const links = npmLinks() + const link = links.find((e) => e.includes(skill.name)) + assert.ok( + link, + `skills/ should contain a symlink for ${skill.name}, found: [${links.join(', ')}]`, + ) + const linkPath = path.join(skillsLinkDir, link) + assert.ok( + fs.lstatSync(linkPath).isSymbolicLink(), + `${link} should be a symlink`, + ) + assert.ok( + fs.existsSync(path.join(linkPath, 'SKILL.md')), + `symlinked skill ${skill.name} should contain SKILL.md`, + ) + }) +} + console.log('\nAll checks passed.')