diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 2dab9d96db..d5b686bb6c 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -2,6 +2,8 @@ name: Verify on: pull_request: + # Labelling a pull request `full-verify` (or removing the label) changes how much it runs. + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] merge_group: workflow_dispatch: @@ -9,16 +11,25 @@ permissions: contents: read # Each merge group has its own gh-readonly-queue ref, so queued groups never cancel each other; the -# queue itself discards a group's run when it rebuilds the group. +# queue itself discards a group's run when it rebuilds the group. A push to a pull request cancels +# its older run. Adding or removing any label other than `full-verify` starts a run in a group of its +# own that verifies nothing, so it never cancels the pull request's real run. concurrency: - group: verify-${{ github.workflow }}-${{ github.event.merge_group.head_ref || github.ref }} + group: >- + verify-${{ github.workflow }}-${{ github.event.merge_group.head_ref || github.ref }}${{ + (github.event.action == 'labeled' || github.event.action == 'unlabeled') + && github.event.label.name != 'full-verify' && format('-label-{0}', github.run_id) || '' }} cancel-in-progress: ${{ github.event_name != 'merge_group' }} jobs: changes: name: Classify verification scope + # A label other than `full-verify` changes nothing this workflow verifies. + if: ${{ !((github.event.action == 'labeled' || github.event.action == 'unlabeled') && github.event.label.name != 'full-verify') }} runs-on: ubuntu-24.04 outputs: + full: ${{ steps.depth.outputs.full }} + partial: ${{ steps.depth.outputs.partial }} checks: ${{ steps.scope.outputs.checks }} compiler_differential: ${{ steps.scope.outputs.compiler_differential }} duckdb: ${{ steps.scope.outputs.duckdb }} @@ -138,6 +149,9 @@ jobs: # skill pages generated from the website docs skips the Python checks and test suites. # The website builds in its own workflow. docs_only=true + # A change that only touches the web UI (and documentation) runs the UI checks and the + # wheels that embed its build, but not the Python and native checks it cannot affect. + ui_only=true while IFS= read -r -d '' path; do changed=true case "${path}" in @@ -147,6 +161,13 @@ jobs: *.md) ;; *) docs_only=false ;; esac + case "${path}" in + ui/*|website/*|.github/workflows/website.yml|.github/demos/*) ;; + src/sqlbuild/.agents/skills/sqlbuild/references/docs/*.md) ;; + */*) ui_only=false ;; + *.md) ;; + *) ui_only=false ;; + esac case "${path}" in crates/*|ui/*|Cargo.toml|Cargo.lock|pyproject.toml|.github/workflows/verify.yml|.github/workflows/build-distributions.yml|deploy/Dockerfile|deploy/project/*|deploy/compose*|compose.yml|.dockerignore|Makefile|tests/integration/src/sqlbuild/compiler/compiled_project/publication/test_compose.py|tests/integration/src/sqlbuild/compiler/compiled_project/publication/docker/compose/*|benchmarks/duckshop/edge_cases/orchestration/container_deployment.json) wheels=true @@ -232,7 +253,7 @@ jobs: fi checks=true - if [[ "${docs_only}" == "true" ]]; then + if [[ "${docs_only}" == "true" || "${ui_only}" == "true" ]]; then checks=false fi @@ -256,8 +277,36 @@ jobs: "${recovery}" \ "${ui}" + # Pull requests run the fast Linux core: static checks, unit and integration tests, native + # workspace checks and the UI checks. The rest of the matrix (wheels, Compose, Windows, macOS, + # DuckDB E2E, warehouse, compiler-golden and timing jobs) runs only once a pull request is + # labelled `full-verify` and is not a draft, and always on merge groups and manual runs. A + # run that leaves out suites its change needs reports under another name, so the required + # Verify stays missing and blocks the merge until the full matrix has passed. + - name: Choose the fast core or the full matrix + id: depth + env: + FULL: >- + ${{ github.event_name != 'pull_request' + || (contains(github.event.pull_request.labels.*.name, 'full-verify') + && !github.event.pull_request.draft) }} + HEAVY_SELECTED: >- + ${{ steps.scope.outputs.duckdb == 'true' || steps.scope.outputs.postgres == 'true' || steps.scope.outputs.clickhouse == 'true' || steps.scope.outputs.performance == 'true' || steps.scope.outputs.format_performance == 'true' || steps.scope.outputs.rules_performance == 'true' || steps.scope.outputs.wheels == 'true' || steps.scope.outputs.compiler_differential == 'true' || steps.scope.outputs.recovery == 'true' }} + run: | + partial=false + if [[ "${FULL}" != "true" && "${HEAVY_SELECTED}" == "true" ]]; then + partial=true + echo "Fast core only: label the pull request full-verify to run the full matrix Verify needs" \ + | tee -a "${GITHUB_STEP_SUMMARY}" + fi + { + echo "full=${FULL}" + echo "partial=${partial}" + } >> "${GITHUB_OUTPUT}" + lockfile: name: Lockfile consistency + if: ${{ !((github.event.action == 'labeled' || github.event.action == 'unlabeled') && github.event.label.name != 'full-verify') }} runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 @@ -365,7 +414,7 @@ jobs: e2e_shards: name: DuckDB E2E (${{ matrix.name }}) needs: [changes, lockfile] - if: needs.changes.outputs.duckdb == 'true' + if: needs.changes.outputs.duckdb == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: @@ -442,7 +491,7 @@ jobs: compiler_differential: name: Compiler golden outputs needs: [changes, lockfile] - if: needs.changes.outputs.compiler_differential == 'true' + if: needs.changes.outputs.compiler_differential == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 steps: @@ -483,7 +532,7 @@ jobs: compiler_differential_python_versions: name: Native compiler on Python ${{ matrix.python }} needs: [changes, lockfile] - if: needs.changes.outputs.compiler_differential == 'true' + if: needs.changes.outputs.compiler_differential == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: @@ -523,7 +572,7 @@ jobs: native_discovery_windows: name: Native compiler on Windows needs: [changes, lockfile] - if: needs.changes.outputs.compiler_differential == 'true' + if: needs.changes.outputs.compiler_differential == 'true' && needs.changes.outputs.full == 'true' runs-on: windows-2025 timeout-minutes: 45 defaults: @@ -580,7 +629,7 @@ jobs: scheduler_recovery: name: Scheduler recovery (${{ matrix.name }}) needs: [changes, lockfile] - if: needs.changes.outputs.recovery == 'true' + if: needs.changes.outputs.recovery == 'true' && needs.changes.outputs.full == 'true' runs-on: ${{ matrix.runner }} timeout-minutes: 75 strategy: @@ -632,7 +681,7 @@ jobs: format_performance: name: Formatter performance guards needs: [changes, lockfile] - if: needs.changes.outputs.format_performance == 'true' + if: needs.changes.outputs.format_performance == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 steps: @@ -696,7 +745,7 @@ jobs: cold_compile_performance: name: Fresh-process compile (${{ matrix.models }} models) needs: [changes, lockfile] - if: needs.changes.outputs.performance == 'true' + if: needs.changes.outputs.performance == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: @@ -747,7 +796,7 @@ jobs: dense_compile_performance: name: Dense all-rules compile (${{ matrix.models }} models) needs: [changes, lockfile] - if: needs.changes.outputs.performance == 'true' + if: needs.changes.outputs.performance == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 # The 3k and 10k cells also run the delta edit-loop guard, about five minutes at 10k. timeout-minutes: 45 @@ -791,7 +840,7 @@ jobs: compile_performance_ratio: name: Same-runner compile ratio (${{ matrix.label || matrix.kind }} 3000 models) needs: [changes, lockfile] - if: needs.changes.outputs.performance == 'true' + if: needs.changes.outputs.performance == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: @@ -894,7 +943,7 @@ jobs: cache_compile_performance: name: Fresh-process compile cache (${{ matrix.models }} models) needs: [changes, lockfile] - if: needs.changes.outputs.performance == 'true' + if: needs.changes.outputs.performance == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 strategy: @@ -1019,7 +1068,7 @@ jobs: postgres: name: PostgreSQL integration and E2E tests needs: [changes, lockfile] - if: needs.changes.outputs.postgres == 'true' + if: needs.changes.outputs.postgres == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 steps: @@ -1056,7 +1105,7 @@ jobs: clickhouse: name: ClickHouse integration and E2E tests needs: [changes, lockfile] - if: needs.changes.outputs.clickhouse == 'true' + if: needs.changes.outputs.clickhouse == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 45 steps: @@ -1093,7 +1142,7 @@ jobs: wheels: name: Wheels needs: [changes] - if: needs.changes.outputs.wheels == 'true' + if: needs.changes.outputs.wheels == 'true' && needs.changes.outputs.full == 'true' uses: ./.github/workflows/build-distributions.yml with: sdist: false @@ -1105,7 +1154,7 @@ jobs: compose: name: Packaged Compose lifecycle needs: [changes, lockfile, wheels] - if: needs.changes.outputs.wheels == 'true' + if: needs.changes.outputs.wheels == 'true' && needs.changes.outputs.full == 'true' runs-on: ubuntu-24.04 timeout-minutes: 20 steps: @@ -1158,11 +1207,16 @@ jobs: # metadata-only pass can never satisfy Verify first; a skipped job would count as passing, so # the job always runs and only its name changes. Until those checks post Verify on the head, # the pull request waits for an expected check, and every new push to the branch waits again. + # The same holds for a fast-core run that leaves out suites its change needs, and for a run + # started by an unrelated label: neither reports as Verify. name: >- ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.event.pull_request.head.ref, 'release-please--branches--')) - && 'Release pull request suites' || 'Verify' }} + && 'Release pull request suites' + || ((github.event.action == 'labeled' || github.event.action == 'unlabeled') && github.event.label.name != 'full-verify') && 'Label change (nothing verified)' + || needs.changes.outputs.partial == 'true' && 'Fast core (label full-verify for Verify)' + || 'Verify' }} if: always() needs: [ @@ -1215,7 +1269,21 @@ jobs: COMPOSE_RESULT: ${{ needs.compose.result }} UI_SELECTED: ${{ needs.changes.outputs.ui }} UI_RESULT: ${{ needs.ui.result }} + LABEL_ONLY: ${{ (github.event.action == 'labeled' || github.event.action == 'unlabeled') && github.event.label.name != 'full-verify' }} + PARTIAL: ${{ needs.changes.outputs.partial }} + DUCKDB_SELECTED: ${{ needs.changes.outputs.duckdb }} + COMPILER_DIFFERENTIAL_SELECTED: ${{ needs.changes.outputs.compiler_differential }} + RECOVERY_SELECTED: ${{ needs.changes.outputs.recovery }} + FORMAT_PERFORMANCE_SELECTED: ${{ needs.changes.outputs.format_performance }} + PERFORMANCE_SELECTED: ${{ needs.changes.outputs.performance }} + POSTGRES_SELECTED: ${{ needs.changes.outputs.postgres }} + CLICKHOUSE_SELECTED: ${{ needs.changes.outputs.clickhouse }} + WHEELS_SELECTED: ${{ needs.changes.outputs.wheels }} run: | + if [[ "${LABEL_ONLY}" == "true" ]]; then + echo "A label other than full-verify changed; this run verifies nothing" + exit 0 + fi if [[ "${CHANGES_RESULT}" != "success" || "${LOCKFILE_RESULT}" != "success" ]]; then echo "Required classification or lockfile checks did not pass" >&2 exit 1 @@ -1258,4 +1326,30 @@ jobs: echo "Required UI checks did not pass: ${UI_RESULT}" >&2 exit 1 fi + # A full run must have run every suite its change selected; a skip here is never a pass. + if [[ "${PARTIAL}" != "true" ]]; then + for pair in \ + "${DUCKDB_SELECTED}:${E2E_SHARDS_RESULT}" \ + "${COMPILER_DIFFERENTIAL_SELECTED}:${COMPILER_DIFFERENTIAL_RESULT}" \ + "${COMPILER_DIFFERENTIAL_SELECTED}:${COMPILER_DIFFERENTIAL_PYTHON_VERSIONS_RESULT}" \ + "${COMPILER_DIFFERENTIAL_SELECTED}:${NATIVE_DISCOVERY_WINDOWS_RESULT}" \ + "${RECOVERY_SELECTED}:${SCHEDULER_RECOVERY_RESULT}" \ + "${FORMAT_PERFORMANCE_SELECTED}:${FORMAT_PERFORMANCE_RESULT}" \ + "${PERFORMANCE_SELECTED}:${COLD_COMPILE_PERFORMANCE_RESULT}" \ + "${PERFORMANCE_SELECTED}:${DENSE_COMPILE_PERFORMANCE_RESULT}" \ + "${PERFORMANCE_SELECTED}:${COMPILE_PERFORMANCE_RATIO_RESULT}" \ + "${PERFORMANCE_SELECTED}:${CACHE_COMPILE_PERFORMANCE_RESULT}" \ + "${POSTGRES_SELECTED}:${POSTGRES_RESULT}" \ + "${CLICKHOUSE_SELECTED}:${CLICKHOUSE_RESULT}" \ + "${WHEELS_SELECTED}:${WHEELS_RESULT}" \ + "${WHEELS_SELECTED}:${COMPOSE_RESULT}"; do + if [[ "${pair%%:*}" == "true" && "${pair#*:}" != "success" ]]; then + echo "A suite this change needs did not run to success: ${pair#*:}" >&2 + exit 1 + fi + done + else + echo "Fast core passed; the full matrix runs once the pull request is labelled full-verify" + exit 0 + fi echo "All path-relevant verification jobs passed" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba45c64ca6..0f7447e387 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,6 +94,14 @@ For broader validation, run: make verify ``` +In CI, every push to a pull request runs a fast Linux core: static checks, unit and integration +tests and native workspace checks, plus the UI checks for UI changes. A change confined to the web +UI and documentation runs only the UI checks. The full matrix (wheels, Compose, Windows, macOS, +DuckDB end-to-end, warehouse, compiler-golden and timing jobs) runs once a maintainer labels a +pull request that is not a draft `full-verify`, normally when it is next to merge. The required +`Verify` check only reports after every suite the change needs has passed, so a pull request that +needs the full matrix cannot merge until it has run. + For focused iteration, run the narrowest relevant pytest target, for example: ```bash diff --git a/Cargo.lock b/Cargo.lock index 5f9152dd78..a04c6ef95a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4126,7 +4126,6 @@ version = "0.0.0" dependencies = [ "memchr", "polyglot-sql", - "polyglot-sql-function-catalogs", "rayon", "regex", "rustc-hash", @@ -4143,7 +4142,9 @@ dependencies = [ "sqlbuild-sqltext", "sqlclean", "sqlquack-syntax", + "sqlquack-syntax-bigquery", "sqlquack-syntax-postgres", + "stacker", ] [[package]] @@ -4200,6 +4201,7 @@ dependencies = [ "same-file", "serde", "serde_json", + "sqlbuild-analysis", "sqlbuild-build-features", "sqlbuild-cache", "sqlbuild-config", @@ -4431,7 +4433,6 @@ dependencies = [ "blake3", "fensu-policy", "globset", - "polyglot-sql", "rayon", "regex", "rustc-hash", @@ -4445,6 +4446,7 @@ dependencies = [ "sqlbuild-scopes", "sqlbuild-sqltext", "sqlclean", + "sqlquack-syntax", "stacker", "tempfile", "toml", @@ -4499,7 +4501,7 @@ dependencies = [ [[package]] name = "sqlquack-syntax" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=4d0c6bc56da0e44eb164601a2af673ae7998257d#4d0c6bc56da0e44eb164601a2af673ae7998257d" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=edfa30b26a0bab86661f96490b86a4f24b92db0a#edfa30b26a0bab86661f96490b86a4f24b92db0a" dependencies = [ "serde", "serde_json", @@ -4509,10 +4511,19 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "sqlquack-syntax-bigquery" +version = "0.2.0" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=edfa30b26a0bab86661f96490b86a4f24b92db0a#edfa30b26a0bab86661f96490b86a4f24b92db0a" +dependencies = [ + "sqlquack-syntax", + "stacker", +] + [[package]] name = "sqlquack-syntax-derive" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=4d0c6bc56da0e44eb164601a2af673ae7998257d#4d0c6bc56da0e44eb164601a2af673ae7998257d" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=edfa30b26a0bab86661f96490b86a4f24b92db0a#edfa30b26a0bab86661f96490b86a4f24b92db0a" dependencies = [ "proc-macro2", "quote", @@ -4522,9 +4533,10 @@ dependencies = [ [[package]] name = "sqlquack-syntax-postgres" version = "0.2.0" -source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=4d0c6bc56da0e44eb164601a2af673ae7998257d#4d0c6bc56da0e44eb164601a2af673ae7998257d" +source = "git+https://github.com/sqlstack-io/sqlquack.git?rev=edfa30b26a0bab86661f96490b86a4f24b92db0a#edfa30b26a0bab86661f96490b86a4f24b92db0a" dependencies = [ "sqlquack-syntax", + "stacker", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f03cafe024..3f05fa9a24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,9 +93,6 @@ polyglot-sql = { git = "https://github.com/sqlstack-io/polyglot.git", rev = "876 "semantic", "stacker", ] } -polyglot-sql-function-catalogs = { git = "https://github.com/sqlstack-io/polyglot.git", rev = "8767d688c5b504ad609645e27ab77a260d91c5a1", default-features = false, features = [ - "dialect-duckdb", -] } blake3 = { version = "1", features = ["rayon"] } # Only sqlbuild-build-features uses these: each enables in the sqb build exactly the features the # extension build resolves for that crate, so both builds share one compile of every common crate. @@ -176,9 +173,10 @@ sqlbuild-sqltext = { path = "crates/sqlbuild-sqltext" } sqlbuild-state = { path = "crates/sqlbuild-state" } # SQL lint Rules, quality Rules and the formatter. sqlclean = { git = "https://github.com/sqlstack-io/sqlclean.git", rev = "cf79656d247277a30bc81ef39b6dee420e721221" } -# The lossless SQL tokenizer behind query fingerprints. -sqlquack-syntax = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "4d0c6bc56da0e44eb164601a2af673ae7998257d" } -sqlquack-syntax-postgres = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "4d0c6bc56da0e44eb164601a2af673ae7998257d" } +# SQLQuack's lossless tokenizer behind query fingerprints and its parsers for warehouse types. +sqlquack-syntax = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "edfa30b26a0bab86661f96490b86a4f24b92db0a" } +sqlquack-syntax-bigquery = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "edfa30b26a0bab86661f96490b86a4f24b92db0a" } +sqlquack-syntax-postgres = { git = "https://github.com/sqlstack-io/sqlquack.git", rev = "edfa30b26a0bab86661f96490b86a4f24b92db0a" } stacker = "0.1" syn = { version = "2", features = ["full", "visit", "extra-traits"] } subtle = "2" diff --git a/benchmarks/duckshop/edge_cases/orchestration/administrator_lifecycle.json b/benchmarks/duckshop/edge_cases/orchestration/administrator_lifecycle.json index 8116485af0..04e310880c 100644 --- a/benchmarks/duckshop/edge_cases/orchestration/administrator_lifecycle.json +++ b/benchmarks/duckshop/edge_cases/orchestration/administrator_lifecycle.json @@ -12,11 +12,11 @@ {"role": "developer", "allowed": true} ], "session_roles": [ - {"role": null, "expected_metadata": 403, "expected_connection": 403, "expected_job": 403, "expected_sql": 403}, - {"role": "viewer", "expected_metadata": 200, "expected_connection": 403, "expected_job": 403, "expected_sql": 403}, - {"role": "operator", "expected_metadata": 200, "expected_connection": 403, "expected_job": 200, "expected_sql": 403}, - {"role": "developer", "expected_metadata": 200, "expected_connection": 200, "expected_job": 200, "expected_sql": 200}, - {"role": "admin", "expected_metadata": 200, "expected_connection": 200, "expected_job": 200, "expected_sql": 200} + {"role": null, "expected_metadata": 403, "expected_connection": 403, "expected_job": 403, "expected_sql": 403, "expected_prioritised": 403, "expected_admin": 403}, + {"role": "viewer", "expected_metadata": 200, "expected_connection": 403, "expected_job": 403, "expected_sql": 403, "expected_prioritised": 403, "expected_admin": 403}, + {"role": "operator", "expected_metadata": 200, "expected_connection": 403, "expected_job": 200, "expected_sql": 403, "expected_prioritised": 403, "expected_admin": 403}, + {"role": "developer", "expected_metadata": 200, "expected_connection": 200, "expected_job": 200, "expected_sql": 200, "expected_prioritised": 403, "expected_admin": 403}, + {"role": "admin", "expected_metadata": 200, "expected_connection": 200, "expected_job": 200, "expected_sql": 200, "expected_prioritised": 200, "expected_admin": 200} ], "job_settings": [ {"full_refresh": false}, diff --git a/clients/typescript/src/schema.d.ts b/clients/typescript/src/schema.d.ts index 2aebecfe52..6d8111d0c9 100644 --- a/clients/typescript/src/schema.d.ts +++ b/clients/typescript/src/schema.d.ts @@ -1,5 +1,37 @@ // Generated from sqb server --openapi. Run npm run generate; do not edit. export interface paths { + "/api/admin/locations/{location_id}/targets/{target}/roles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listRoleDefinitions"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/admin/people": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listPeople"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/admin/people/{principal_id}": { parameters: { query?: never; @@ -23,7 +55,7 @@ export interface paths { path?: never; cookie?: never; }; - get?: never; + get: operations["listRoleHoldings"]; put?: never; post: operations["assignBuiltinRole"]; delete?: never; @@ -354,6 +386,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/control/locations/{location_id}/targets/{target}/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["readPersonLocationHealth"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/control/locations/{location_id}/targets/{target}/jobs": { parameters: { query?: never; @@ -434,6 +482,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/runs": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listPersonNodeRuns"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/sql": { parameters: { query?: never; @@ -466,6 +530,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/events": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listPersonRunEvents"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/nodes": { parameters: { query?: never; @@ -482,6 +562,54 @@ export interface paths { patch?: never; trace?: never; }; + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/output": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["readPersonRunOutput"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["listPersonRunStatements"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements/sql": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["readPersonRunStatementSql"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/control/locations/{location_id}/targets/{target}/snapshots": { parameters: { query?: never; @@ -546,6 +674,22 @@ export interface paths { patch?: never; trace?: never; }; + "/api/control/platform/problems": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get: operations["readPersonPlatformProblems"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/control/runs": { parameters: { query?: never; @@ -562,6 +706,38 @@ export interface paths { patch?: never; trace?: never; }; + "/api/control/runs/adhoc": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["requestAdhocRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/control/runs/adhoc/check": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["checkAdhocRun"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/control/runs/cancel": { parameters: { query?: never; @@ -954,7 +1130,50 @@ export interface components { * @description Closed permission vocabulary; unknown actions fail deserialization. * @enum {string} */ - Action: "view" | "view.sql" | "view.evidence" | "job.run" | "run.cancel" | "run.rerun" | "run.rewrite_history" | "trigger.toggle" | "sensor.reset_cursor" | "run.adhoc" | "connection.test" | "user.manage" | "role.assign" | "audit.view"; + Action: "view" | "view.sql" | "view.evidence" | "job.run" | "run.cancel" | "run.rerun" | "run.rewrite_history" | "run.prioritise" | "run.warehouse" | "trigger.toggle" | "sensor.reset_cursor" | "run.adhoc" | "connection.test" | "user.manage" | "role.assign" | "audit.view"; + /** @description What launching the command would run and whether this person may launch it. */ + AdhocCheck: { + /** @description Why the command cannot run regardless of permissions, such as a refused flag or an unknown selector. */ + error?: string | null; + launchable: boolean; + permissions: components["schemas"]["AdhocPermission"][]; + selection: string[]; + settings: { + [key: string]: unknown; + }; + /** Format: uuid */ + snapshot_id?: string | null; + verb?: string | null; + write_set: string[]; + }; + /** @description A dry run of a command; nothing is queued or audited. */ + AdhocCheckRequest: { + command: string[]; + /** Format: uuid */ + location_id: string; + /** Format: int64 */ + priority: number; + target: string; + }; + /** @description One permission the run needs and whether the person holds it on every resolved node. */ + AdhocPermission: { + action: components["schemas"]["Action"]; + allowed: boolean; + /** @description Why it is refused, as the launch would report it. */ + reason?: string | null; + }; + /** @description A person's command as argument tokens after `sqb`, never shell text; selectors resolve on the server. */ + AdhocRunRequest: { + command: string[]; + /** Format: uuid */ + location_id: string; + /** Format: int64 */ + priority: number; + reason: string; + /** Format: uuid */ + run_id: string; + target: string; + }; /** * @description Administrator membership and person eligibility share the final-admin invariant. * @enum {string} @@ -978,6 +1197,15 @@ export interface components { * @enum {string} */ BatchState: "complete" | "gap" | "replay_required"; + /** @description Every instance of this location target has missed two check-ins. */ + BrokenLocation: { + /** Format: date-time */ + last_seen_at: string; + /** Format: uuid */ + location_id: string; + location_name: string; + target: string; + }; /** @enum {string} */ BuiltinRole: "viewer" | "operator" | "developer" | "admin"; /** @description Browser cancellation cannot supply an actor, claim or lifecycle transition. */ @@ -1197,6 +1425,14 @@ export interface components { source_event_id: string; version_hash: string; }; + /** @description A role every member of an SSO group holds. */ + GroupRole: { + group_name: string; + /** Format: uuid */ + location_id?: string | null; + role_key: string; + target?: string | null; + }; HeartbeatResponse: { cancel_requested: boolean; }; @@ -1409,6 +1645,23 @@ export interface components { NodeEventKind: "success" | "failed" | "recovered"; /** @enum {string} */ NodeOutcome: "success" | "warn" | "failed" | "skipped" | "cancelled"; + /** @description One build attempt of a node by a run in this target; the same metadata as the run's node list. */ + NodeRunRecord: { + /** Format: date-time */ + ended_at?: string | null; + error_code?: string | null; + job_key?: string | null; + /** Format: date-time */ + queued_at: string; + /** Format: int64 */ + rows_affected?: number | null; + /** Format: uuid */ + run_id: string; + skipped_because_of?: string | null; + /** Format: date-time */ + started_at?: string | null; + status: string; + }; /** @description SQL inspection never returns Python source, execution artifacts or unrelated node metadata. */ NodeSql: { compiled_sql?: string | null; @@ -1472,6 +1725,18 @@ export interface components { * @enum {string} */ OutputStream: "stdout" | "stderr"; + /** @description A running schedule whose next tick is more than 30 seconds late. */ + OverdueTick: { + /** Format: uuid */ + location_id: string; + location_name: string; + name: string; + /** Format: date-time */ + scheduled_for: string; + target: string; + /** Format: uuid */ + trigger_id: string; + }; PasswordChangeRequest: { current_password: string; new_password: string; @@ -1492,6 +1757,42 @@ export interface components { /** Format: uuid */ principal_id: string; }; + /** @description A role assigned to one person, everywhere or on one location and target. */ + PersonRole: { + /** Format: uuid */ + assignment_id: string; + /** Format: uuid */ + location_id?: string | null; + name: string; + /** Format: uuid */ + principal_id: string; + role_key: string; + target?: string | null; + }; + /** @description A person as administrators see them: how they sign in, their SSO groups and live sessions. */ + PersonSummary: { + /** + * Format: int64 + * @description Unrevoked, unexpired browser and CLI sessions; none while the person is disabled. + */ + active_sessions: number; + /** Format: date-time */ + created_at: string; + /** Format: date-time */ + disabled_at?: string | null; + /** @description Groups the identity provider reported at the person's last SSO sign-in. */ + groups: string[]; + has_password: boolean; + /** Format: date-time */ + last_signed_in_at?: string | null; + name: string; + /** Format: uuid */ + principal_id: string; + }; + /** @description A trigger as people list it, with its newest tick so a list needs no read per trigger. */ + PersonTriggerRecord: components["schemas"]["TriggerRecord"] & { + latest_tick?: null | components["schemas"]["TickRecord"]; + }; /** @description Observed physical evidence is not a live warehouse-existence guarantee. */ PhysicalEvidence: { observation: components["schemas"]["CatalogObservation"]; @@ -1504,6 +1805,17 @@ export interface components { }; /** @enum {string} */ PlatformEventKind: "heartbeat" | "scheduler_no_leader" | "tick_overdue" | "calendar_horizon_low" | "location_broken" | "launch_failed"; + /** @description Problems derived from live state when read; nothing here is stored. */ + PlatformProblems: { + broken_locations: components["schemas"]["BrokenLocation"][]; + overdue_ticks: components["schemas"]["OverdueTick"][]; + scheduler_leader: boolean; + /** + * Format: date-time + * @description When the last scheduler lease ran out, or will run out; absent until a server first leads. + */ + scheduler_lease_expires_at?: string | null; + }; /** @description A changed subject, such as `model:orders`, or `role:analyst` (`role:` for those who can't assign roles). */ PublishedChange: { change_kind: string; @@ -1580,8 +1892,35 @@ export interface components { role: components["schemas"]["RoleKey"]; target?: string | null; }; + /** @description Who holds which role where: assigned to people here, or mapped to SSO groups in server config. */ + RoleHoldings: { + assignments: components["schemas"]["PersonRole"][]; + group_roles: components["schemas"]["GroupRole"][]; + }; /** @description A built-in role by name, or a custom role a location publishes. */ RoleKey: components["schemas"]["BuiltinRole"] | string; + /** @description A role usable on one location target: built in, or published by the location's project. */ + RoleOverview: { + builtin: boolean; + /** @description The project's description; absent for built-in roles. */ + description?: string | null; + /** @description Built-in roles: one grant without selectors, meaning every node. Custom roles: their published grants. */ + grants: components["schemas"]["RoleOverviewGrant"][]; + /** + * Format: int64 + * @description Active people holding the role here or everywhere, assigned directly or through an SSO group. + */ + holders: number; + key: string; + }; + /** @description Node selectors, the actions they grant, and the nodes each selector matched when published. */ + RoleOverviewGrant: { + matched: { + [key: string]: string[]; + }; + nodes: string[]; + permissions: components["schemas"]["Action"][]; + }; /** @description This is a server-owned instruction, not an arbitrary status supplied by a worker. */ RunChange: { container_id?: string | null; @@ -1650,8 +1989,39 @@ export interface components { claim_id: string; request: components["schemas"]["RunRequest"]; }; + RunErrorText: { + error_type?: string | null; + invocation_id: string; + message: string; + occurred_at?: string | null; + }; /** @enum {string} */ RunEventKind: "completed" | "success" | "warn" | "partial" | "failed" | "cancelled"; + /** @description Recall event metadata of a run. Payloads are left out: they can carry SQL, request settings and error text. */ + RunEventRecord: { + /** Format: int64 */ + affected_rows?: number | null; + /** Format: double */ + duration_ms?: number | null; + error_code?: string | null; + error_type?: string | null; + event_id: string; + event_type: string; + invocation_id: string; + occurred_at: string; + outcome?: string | null; + producer?: string | null; + resource_attempt_id?: string | null; + resource_id?: string | null; + resource_kind?: string | null; + resource_name?: string | null; + /** Format: int64 */ + row_count?: number | null; + severity?: string | null; + statement_id?: string | null; + /** Format: int64 */ + storage_id: number; + }; /** @description Workers compile locally and compare against this immutable snapshot, not the current deploy. */ RunExecution: { claim: components["schemas"]["RunClaim"]; @@ -1686,6 +2056,21 @@ export interface components { * @enum {string} */ RunOutcome: "success" | "warn" | "partial" | "failed" | "cancelled"; + /** @description Captured output and error text of a run's invocations; both can quote warehouse data. */ + RunOutput: { + /** @description Invocation failures and error diagnostics, oldest first. */ + errors: components["schemas"]["RunErrorText"][]; + /** @description The newest captured lines per invocation, oldest first. */ + lines: components["schemas"]["RunOutputLine"][]; + }; + RunOutputLine: { + invocation_id: string; + occurred_at: string; + /** Format: int64 */ + sequence: number; + stream: string; + text: string; + }; RunRecord: { /** Format: date-time */ cancel_requested_at?: string | null; @@ -1768,6 +2153,36 @@ export interface components { window?: null | components["schemas"]["RunWindow"]; write_set: string[]; }; + /** @description Warehouse statement metadata of a run: kind, timing, counts and outcome, never the SQL text. */ + RunStatementRecord: { + /** Format: int64 */ + affected_rows?: number | null; + /** Format: double */ + duration_ms?: number | null; + ended_at?: string | null; + error_code?: string | null; + error_type?: string | null; + intent?: string | null; + invocation_id: string; + outcome?: string | null; + purpose?: string | null; + query_id?: string | null; + resource_id?: string | null; + resource_kind?: string | null; + resource_name?: string | null; + /** Format: int64 */ + row_count?: number | null; + sql_available: boolean; + sql_unavailable_reason?: string | null; + started_at?: string | null; + statement_id: string; + statement_kind?: string | null; + }; + /** @description One statement's executed SQL; `sql` is null when capture was off or retention dropped it. */ + RunStatementSql: { + sql?: string | null; + statement_id: string; + }; /** @enum {string} */ RunStatus: "queued" | "launched" | "running" | "unresponsive" | "success" | "warn" | "partial" | "failed" | "cancelled"; /** @description Inclusive typed CLI cursor bounds; the native planner derives exclusive execution ends. */ @@ -1802,6 +2217,8 @@ export interface components { SecurityAuditEvent: { action: string; actor: string; + /** @description The person's name when `actor` is a principal; person reads set it, exports leave it out. */ + actor_name?: string | null; after_json: unknown; before_json: unknown; /** Format: uuid */ @@ -2168,6 +2585,78 @@ export interface components { } export type $defs = Record; export interface operations { + listRoleDefinitions: { + parameters: { + query?: never; + header?: never; + path: { + location_id: string; + target: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Built-in roles, then the target's published custom roles */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RoleOverview"][]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + }; + }; + listPeople: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PersonSummary"][]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + }; + }; changeAdministrator: { parameters: { query?: never; @@ -2212,6 +2701,40 @@ export interface operations { }; }; }; + listRoleHoldings: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RoleHoldings"]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + }; + }; assignBuiltinRole: { parameters: { query?: never; @@ -2540,6 +3063,8 @@ export interface operations { query?: { /** @description Exclusive storage-order cursor */ after?: number; + /** @description Exclusive upper cursor; when set, the page is newest first */ + before?: number; limit?: number; }; header?: never; @@ -2996,6 +3521,44 @@ export interface operations { }; }; }; + readPersonLocationHealth: { + parameters: { + query?: never; + header?: never; + path: { + location_id: string; + target: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description This target's newest health observations and instance check-ins */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["LocationHealthObservation"][]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + }; + }; listPersonJobs: { parameters: { query?: never; @@ -3202,6 +3765,48 @@ export interface operations { }; }; }; + listPersonNodeRuns: { + parameters: { + query?: { + /** @description Maximum recent runs; defaults to 100, capped at 1000 */ + limit?: number; + }; + header?: never; + path: { + location_id: string; + target: string; + node_key: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Runs that built the node, newest first */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["NodeRunRecord"][]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + }; + }; readPersonNodeSql: { parameters: { query?: never; @@ -3279,6 +3884,57 @@ export interface operations { }; }; }; + listPersonRunEvents: { + parameters: { + query?: { + /** @description Return events after this storage cursor */ + after?: number; + /** @description Maximum events; defaults to 1000, capped at 5000 */ + limit?: number; + }; + header?: never; + path: { + location_id: string; + target: string; + run_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Event metadata oldest first, without payloads */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RunEventRecord"][]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + /** @description Run not found for a person who may view the whole target */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; listPersonRunNodes: { parameters: { query?: never; @@ -3324,6 +3980,150 @@ export interface operations { }; }; }; + readPersonRunOutput: { + parameters: { + query?: never; + header?: never; + path: { + location_id: string; + target: string; + run_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RunOutput"]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Needs view.evidence on every node the run selected */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + /** @description Run not found for a person who may view the whole target */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + listPersonRunStatements: { + parameters: { + query?: { + /** @description Maximum statements; defaults to 1000, capped at 5000 */ + limit?: number; + }; + header?: never; + path: { + location_id: string; + target: string; + run_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Statement metadata in start order, without SQL */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RunStatementRecord"][]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + /** @description Run not found for a person who may view the whole target */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + readPersonRunStatementSql: { + parameters: { + query: { + /** @description One statement of this run */ + statement_id: string; + }; + header?: never; + path: { + location_id: string; + target: string; + run_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RunStatementSql"]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Needs view.sql on every node the run selected */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + /** @description Run or statement not found */ + 404: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; listPersonSnapshots: { parameters: { query?: { @@ -3429,12 +4229,13 @@ export interface operations { }; requestBody?: never; responses: { + /** @description Published triggers with their newest tick */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["TriggerRecord"][]; + "application/json": components["schemas"]["PersonTriggerRecord"][]; }; }; /** @description Session rejected */ @@ -3502,6 +4303,33 @@ export interface operations { }; }; }; + readPersonPlatformProblems: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Live scheduler state, and broken locations and late ticks of targets the person lists */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PlatformProblems"]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; requestJobRun: { parameters: { query?: never; @@ -3542,6 +4370,86 @@ export interface operations { }; }; }; + requestAdhocRun: { + parameters: { + query?: never; + header: { + "x-sqb-csrf": string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AdhocRunRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResponse"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiErrorBody"]; + }; + }; + }; + }; + checkAdhocRun: { + parameters: { + query?: never; + header: { + "x-sqb-csrf": string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AdhocCheckRequest"]; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AdhocCheck"]; + }; + }; + /** @description Session rejected */ + 401: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; cancelPersonRun: { parameters: { query?: never; @@ -4229,6 +5137,20 @@ export interface operations { }; content?: never; }; + /** @description Body over its wire or decompressed bound */ + 413: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Content encoding other than gzip */ + 415: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; }; }; metadata: { diff --git a/crates/sqlbuild-analysis/Cargo.toml b/crates/sqlbuild-analysis/Cargo.toml index 80a5ae2313..3471a267d5 100644 --- a/crates/sqlbuild-analysis/Cargo.toml +++ b/crates/sqlbuild-analysis/Cargo.toml @@ -10,7 +10,6 @@ rust-version.workspace = true [dependencies] memchr.workspace = true polyglot-sql.workspace = true -polyglot-sql-function-catalogs.workspace = true rayon.workspace = true regex.workspace = true rustc-hash.workspace = true @@ -27,7 +26,9 @@ sqlbuild-scopes.workspace = true sqlbuild-sqltext.workspace = true sqlclean.workspace = true sqlquack-syntax.workspace = true +sqlquack-syntax-bigquery.workspace = true sqlquack-syntax-postgres.workspace = true +stacker.workspace = true [lints] workspace = true diff --git a/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dict_walk.rs b/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dict_walk.rs index 3daa93527a..9602a08aab 100644 --- a/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dict_walk.rs +++ b/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dict_walk.rs @@ -6,6 +6,7 @@ use polyglot_sql::{ComplexityGuardOptions, DataType, Dialect, Expression, ParseO use serde_json::{Map, Value, json}; use crate::assembly::analysis_session::models::ProjectionFacts; +use crate::relation_markers::constants::MARKER_NAME_PREFIX; use crate::assembly::analysis_session::constants::{ ALIAS_AST_KIND, ANNOTATED_AST_KIND, ARRAY_FIXTURE_TYPE, ARRAY_PAYLOAD_KIND, CATALOG_TABLE_ARG, @@ -95,7 +96,8 @@ pub(crate) fn relation_name(node: Option<&Value>) -> Option<&str> { let key: &str = node_key(node); let payload: &Map = payload(node)?; if key == TABLE_AST_KIND || key == COLUMN_AST_KIND { - return identifier_name(payload.get("name")); + return identifier_name(payload.get("name")) + .filter(|name| !name.starts_with(MARKER_NAME_PREFIX)); } let called: &str = payload .get("name") diff --git a/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dynamic_pivot.rs b/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dynamic_pivot.rs index e49ac7c85c..76695224b6 100644 --- a/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dynamic_pivot.rs +++ b/crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dynamic_pivot.rs @@ -7,20 +7,28 @@ use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use serde_json::{Map, Value}; use sqlbuild_core::panics::main::catch_compiler_panic::catch_compiler_panic; use sqlbuild_core::panics::main::native_failure::native_failure; +use sqlbuild_sqltext::sql_scan::main::dialect_lexical_syntax::dialect_lexical_syntax; +use sqlquack_syntax::{CallMode, Dialect, Expression, ParseOptions}; use crate::assembly::analysis_session::_helpers::dict_walk::{ casefold, column_name, dict_list, identifier_name, is_single_wildcard, nested, node_key, - parse_one_or_error, payload, relation_name, render_type, to_dict, truthy, upper, + payload, relation_name, render_type, truthy, upper, }; use crate::assembly::analysis_session::constants::{ - ALIAS_AST_KIND, CAST_AST_KIND, DUCKDB_DIALECT, DYNAMIC_VALUE_SOURCE_KINDS, MOTHERDUCK_DIALECT, - PIVOT_AST_KIND, QUERY_AST_KINDS, SELECT_AST_KIND, SIMPLIFIED_PIVOT_DIALECTS, - SUPPORTED_PIVOT_DIALECTS, TYPE_PRESERVING_AGGREGATES, UNKNOWN_NULLABILITY, UNKNOWN_TYPE, + ALIAS_AST_KIND, CAST_AST_KIND, COLUMN_AST_KIND, DEPENDENCY_FUNCTION_NAMES, DUCKDB_DIALECT, + DYNAMIC_VALUE_SOURCE_KINDS, MOTHERDUCK_DIALECT, PIVOT_AST_KIND, PIVOT_MAX_AST_DEPTH, + QUERY_AST_KINDS, SELECT_AST_KIND, SIMPLIFIED_PIVOT_DIALECTS, SNOWFLAKE_DIALECT, + SUPPORTED_PIVOT_DIALECTS, TABLE_AST_KIND, TYPE_PRESERVING_AGGREGATES, UNKNOWN_NULLABILITY, + UNKNOWN_TYPE, }; use crate::assembly::analysis_session::models::{ ColumnFact, ContractProof, DynamicFamily, PivotModel, PivotOutcome, PivotTables, }; use crate::assembly::analysis_session::types::Shapes; +use crate::relation_markers::constants::MARKER_NAME_PREFIX; +use crate::relation_markers::main::markers_as_names::markers_as_names; +use crate::relation_markers::types::MarkerCall; +use crate::sql_tree::main::deeper_than::deeper_than; use sqlbuild_core::constants::CTE_AST_KIND; /// What an internal native failure of a pivot proof names. @@ -115,11 +123,81 @@ pub(crate) fn pivot_outcome( .map_err(|reason| native_failure(PIVOT_CONTEXT, &reason)) } -/// Python's `parse_one(...).to_dict()`, or the parse error Python reports. +/// The one statement of `sql` as SQLQuack's tagged JSON tree, marker relations as their names. fn parsed(sql: &str, dialect: &str) -> Result, String> { - parse_one_or_error(sql, dialect)? - .map(|expression| to_dict(&expression)) - .map_or_else(|error| Ok(Err(error)), |value| value.map(Ok)) + let (lexical_dialect, dialect): (&str, Dialect) = if dialect == SNOWFLAKE_DIALECT { + (SNOWFLAKE_DIALECT, Dialect::Snowflake) + } else { + (DUCKDB_DIALECT, Dialect::DuckDB) + }; + let (named, calls) = markers_as_names(sql, &dialect_lexical_syntax(lexical_dialect))?; + let parsed = sqlquack_syntax::parse_statements_with( + &named, + dialect, + &ParseOptions::default(), + CallMode::Faithful, + ); + let mut statements: Vec = match parsed { + Ok(statements) => statements, + Err(error) => return Ok(Err(error.to_string())), + }; + if statements.len() != 1 { + return Ok(Err(format!( + "Expected 1 statement, found {}", + statements.len() + ))); + } + let statement: Expression = statements.pop().ok_or_else(|| "no statement".to_owned())?; + if deeper_than(&statement, PIVOT_MAX_AST_DEPTH) { + return Ok(Err(format!( + "E_GUARD_AST_DEPTH_EXCEEDED: value {} exceeds configured limit {PIVOT_MAX_AST_DEPTH}", + PIVOT_MAX_AST_DEPTH + 1 + ))); + } + let mut root: Value = serde_json::to_value(statement).map_err(|error| error.to_string())?; + name_marker_tables(&mut root, &calls); + Ok(Ok(root)) +} + +/// Each `__ref`, `__source` or `__seed` marker, a table or column node, renamed to the relation its call names. +fn name_marker_tables(root: &mut Value, calls: &[MarkerCall]) { + let mut stack: Vec<&mut Value> = vec![root]; + while let Some(node) = stack.pop() { + match node { + Value::Object(object) => { + for kind in [TABLE_AST_KIND, COLUMN_AST_KIND] { + if let Some(Value::Object(relation)) = object.get_mut(kind) { + rename_marker(relation.get_mut("name"), calls); + } + } + stack.extend(object.values_mut()); + } + Value::Array(items) => stack.extend(items.iter_mut()), + _ => {} + } + } +} + +/// The marker name in `name`, a plain string or an identifier object, replaced by its call's relation. +fn rename_marker(name: Option<&mut Value>, calls: &[MarkerCall]) { + let text: Option<&mut Value> = match name { + Some(Value::Object(identifier)) => identifier.get_mut("name"), + other => other, + }; + let Some(Value::String(text)) = text else { + return; + }; + let Some(Ok(index)) = text + .strip_prefix(MARKER_NAME_PREFIX) + .map(str::parse::) + else { + return; + }; + if let Some((function, relation)) = calls.get(index) + && DEPENDENCY_FUNCTION_NAMES.contains(&function.as_str()) + { + *text = relation.clone(); + } } fn proof( @@ -362,6 +440,18 @@ fn has_dynamic_values(field: &Value) -> bool { .any(|value| DYNAMIC_VALUE_SOURCE_KINDS.contains(&node_key(Some(value)))) } +/// The one argument of a call SQLQuack keeps by name, such as Snowflake's `SUM(amount)`. +fn sole_argument(payload: &Map) -> Option<&Value> { + match payload + .get("args") + .and_then(Value::as_array) + .map(Vec::as_slice) + { + Some([argument]) => Some(argument), + _ => None, + } +} + /// Python's `_aggregate_fact`: aggregate name, value column and type-preserving inferred type. fn aggregate_fact<'a>( aggregate: &'a Value, @@ -380,7 +470,7 @@ fn aggregate_fact<'a>( name = upper(authored); } let preserving: bool = TYPE_PRESERVING_AGGREGATES.contains(&name.as_str()); - let value: Option<&Value> = payload.get("this"); + let value: Option<&Value> = payload.get("this").or_else(|| sole_argument(payload)); let mut value_name: Option<&str> = column_name(value); let mut inferred: Option = None; let cast: Option<&Map> = value diff --git a/crates/sqlbuild-analysis/src/assembly/analysis_session/constants.rs b/crates/sqlbuild-analysis/src/assembly/analysis_session/constants.rs index 5da96a56e2..106d04d447 100644 --- a/crates/sqlbuild-analysis/src/assembly/analysis_session/constants.rs +++ b/crates/sqlbuild-analysis/src/assembly/analysis_session/constants.rs @@ -63,6 +63,7 @@ pub(crate) const SUPPORTED_PIVOT_DIALECTS: [&str; 3] = ["duckdb", "motherduck", pub(crate) const SIMPLIFIED_PIVOT_DIALECTS: [&str; 2] = ["duckdb", "motherduck"]; pub(crate) const DUCKDB_DIALECT: &str = "duckdb"; pub(crate) const MOTHERDUCK_DIALECT: &str = "motherduck"; +pub(crate) const SNOWFLAKE_DIALECT: &str = "snowflake"; /// The `to_dict()` node keys Python's dynamic pivot proof walks. pub(crate) const PIVOT_AST_KIND: &str = "pivot"; pub(crate) const SELECT_AST_KIND: &str = "select"; @@ -93,6 +94,8 @@ pub(crate) const RENDERED_TYPE_NAMES: [(&str, &str); 12] = [ ]; /// The stack a standalone dynamic pivot proof parses on, as the analysis workers have. pub(crate) const PIVOT_WORKER_STACK_BYTES: usize = 16 * 1024 * 1024; +/// The deepest pivot statement the proof serialises; deeper SQL fails the proof instead of the stack. +pub(crate) const PIVOT_MAX_AST_DEPTH: usize = 512; /// Python's `COMPACT_RELATION_STUB_PREFIX`: the relation name a shared query binds instead. pub(crate) const RELATION_STUB_PREFIX: &str = "__sqlbuild_project_input_"; /// Python's `COMPACT_REFERENCE_MARKER_PATTERN`: a reference call and the name it reads. diff --git a/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_analysis_session.rs b/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_analysis_session.rs index 77aab0583f..b28cd18944 100644 --- a/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_analysis_session.rs +++ b/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_analysis_session.rs @@ -9,9 +9,9 @@ use crate::assembly::analysis_session::tests::helpers::{ session_lines, session_steps, }; use crate::assembly::analysis_session::tests::test_types::{ - CteRecoveryTestCase, KeptAnalysisTestCase, LegacyAnalysisTestCase, ModelSpec, PivotTestCase, - SessionFactsTestCase, SessionFailureTestCase, SessionTestCase, StarExpansionTestCase, - UnscheduledTestCase, + CteRecoveryTestCase, DeepPivotTestCase, KeptAnalysisTestCase, LegacyAnalysisTestCase, + ModelSpec, PivotTestCase, SessionFactsTestCase, SessionFailureTestCase, SessionTestCase, + StarExpansionTestCase, UnscheduledTestCase, }; /// A model whose missing column stays untyped over a known input, so completion re-analyses it. @@ -444,6 +444,32 @@ fn given_dynamic_pivots_when_proving_then_matches_python_or_defers() { false, ), }, + PivotTestCase { + description: "a Snowflake cast aggregate renders its target type", + dialect: "snowflake", + sql: "WITH base AS (SELECT customer_id, status, amount FROM __source(\"raw_orders\")) \ + SELECT * FROM base \ + PIVOT (MAX(CAST(amount AS DECIMAL(12, 2))) FOR status IN (ANY ORDER BY status))", + family: Some(("status", "amount", "MAX")), + expected_outcome: proven( + &[("customer_id", "INTEGER", "non_null")], + Some("DECIMAL(12, 2)"), + "raw_orders", + false, + ), + }, + PivotTestCase { + description: "a Snowflake passthrough reads its relation marker as the relation", + dialect: "snowflake", + sql: "SELECT * FROM __ref(\"status_amounts\")", + family: Some(("status", "amount", "MAX")), + expected_outcome: proven( + &[("customer_id", "INTEGER", "unknown")], + Some("DOUBLE"), + "status_amounts", + false, + ), + }, PivotTestCase { description: "a passthrough redeclaring the upstream families", dialect: "duckdb", @@ -512,6 +538,16 @@ fn given_dynamic_pivots_when_proving_then_matches_python_or_defers() { families exactly", ), }, + PivotTestCase { + description: "a passthrough from a call that names no dependency", + dialect: "duckdb", + sql: "SELECT * FROM __dbt_ref(\"shop\", \"status_amounts\")", + family: Some(("status", "amount", "MAX")), + expected_outcome: failed( + "output must be a wildcard projection from one supported dynamic pivot or a \ + proven dynamic-family passthrough", + ), + }, PivotTestCase { description: "no declared families", dialect: "duckdb", @@ -742,3 +778,52 @@ fn given_a_finished_session_when_reading_model_analyses_then_keeps_typed_columns ); } } + +#[test] +fn given_deeply_nested_pivot_input_when_proving_then_the_proof_fails_without_aborting() { + let too_deep = failed( + "dynamic pivot SQL could not be parsed: \ + E_GUARD_AST_DEPTH_EXCEEDED: value 513 exceeds configured limit 512", + ); + let test_cases = [ + DeepPivotTestCase { + description: "a 50,000-term DuckDB projection", + dialect: "duckdb", + terms: 50_000, + expected_outcome: too_deep.clone(), + }, + DeepPivotTestCase { + description: "a 50,000-term Snowflake projection", + dialect: "snowflake", + terms: 50_000, + expected_outcome: too_deep, + }, + ]; + for test_case in test_cases { + let sql = format!( + "WITH base AS (SELECT customer_id, status, amount, 1{} AS k \ + FROM __source(\"raw_orders\")) \ + SELECT * FROM base PIVOT (MAX(amount) FOR status IN (ANY ORDER BY status))", + " + 1".repeat(test_case.terms) + ); + let request = pivot_request(test_case.dialect, &sql, Some(("status", "amount", "MAX"))); + let session = finished_session(request.tables.clone()); + + let standalone = prove_dynamic_contracts(&request).expect("the proofs run"); + let in_session = + prove_finished_dynamic_contracts(&session, &request.models).expect("the proofs run"); + + assert_eq!( + standalone, + vec![test_case.expected_outcome.clone(), PivotOutcome::Absent], + "{}", + test_case.description + ); + assert_eq!( + in_session, + vec![test_case.expected_outcome, PivotOutcome::Absent], + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_types.rs b/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_types.rs index aa29f89c8d..1c338650e8 100644 --- a/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_types.rs +++ b/crates/sqlbuild-analysis/src/assembly/analysis_session/tests/test_types.rs @@ -230,3 +230,11 @@ pub(crate) struct TypedMarkerCacheTestCase { pub(crate) sql: &'static str, pub(crate) expected_hits: &'static [bool], } + +/// A pivot over a CTE whose projection is a left-nested sum of `terms` ones. +pub(crate) struct DeepPivotTestCase { + pub(crate) description: &'static str, + pub(crate) dialect: &'static str, + pub(crate) terms: usize, + pub(crate) expected_outcome: PivotOutcome, +} diff --git a/crates/sqlbuild-analysis/src/contracts/tests/test_evaluate_model_contracts.rs b/crates/sqlbuild-analysis/src/contracts/tests/test_evaluate_model_contracts.rs index d1a7294798..4573a0010a 100644 --- a/crates/sqlbuild-analysis/src/contracts/tests/test_evaluate_model_contracts.rs +++ b/crates/sqlbuild-analysis/src/contracts/tests/test_evaluate_model_contracts.rs @@ -282,11 +282,11 @@ fn given_models_sharing_types_when_evaluating_together_then_each_matches_its_own } #[test] -fn given_a_dialect_polyglot_does_not_know_when_comparing_types_then_python_error_is_returned() { +fn given_a_dialect_no_adapter_ships_when_comparing_types_then_python_error_is_returned() { let test_cases = [UnknownDialectTestCase { description: "an enforced contract compared under an unknown dialect", - dialect: "motherduck", - expected_error: "Unknown dialect: motherduck", + dialect: "mysql", + expected_error: "Unknown dialect: mysql", }]; for test_case in test_cases { let request = ContractRequest { diff --git a/crates/sqlbuild-analysis/src/lib.rs b/crates/sqlbuild-analysis/src/lib.rs index 5ad16bf8b6..848c4de883 100644 --- a/crates/sqlbuild-analysis/src/lib.rs +++ b/crates/sqlbuild-analysis/src/lib.rs @@ -37,6 +37,7 @@ pub mod python_plan; pub mod python_selection; pub mod python_values; pub mod query_analysis; +pub mod relation_markers; pub mod relation_names; pub mod required_columns; pub mod resource_diagnostics; @@ -51,4 +52,5 @@ pub mod sql_tokens; pub mod sql_tree; pub mod target_paths; pub mod type_system; +pub mod warehouse_types; pub mod worker_stack; diff --git a/crates/sqlbuild-analysis/src/planner_strategy/main/classify_cursor_sql_type.rs b/crates/sqlbuild-analysis/src/planner_strategy/main/classify_cursor_sql_type.rs index 22dedf16bd..1636707d0d 100644 --- a/crates/sqlbuild-analysis/src/planner_strategy/main/classify_cursor_sql_type.rs +++ b/crates/sqlbuild-analysis/src/planner_strategy/main/classify_cursor_sql_type.rs @@ -7,9 +7,9 @@ use crate::planner_strategy::_helpers::type_names::{argument, named_type}; use crate::planner_strategy::constants::{GENERIC_DIALECT, POLYGLOT_CUSTOM_TYPE_NAME}; use crate::planner_strategy::errors::TypeParseFailure; use crate::planner_strategy::models::CursorType; -use crate::type_system::main::polyglot_type_args::polyglot_type_args; +use crate::type_system::main::type_args::type_args; -/// Timestamp or integer for supported cross-adapter types; `Err` when Polyglot cannot parse an unknown type. +/// Timestamp or integer for supported cross-adapter types; `Err` when an unknown type does not parse. pub fn classify_cursor_sql_type(sql_type: &str) -> Result, TypeParseFailure> { let normalized: String = sql_type .to_uppercase() @@ -29,7 +29,7 @@ pub fn classify_cursor_sql_type(sql_type: &str) -> Result, Ty return Ok(Some(cursor_type)); } let args: Map = - polyglot_type_args(&normalized, GENERIC_DIALECT).map_err(|message| TypeParseFailure { + type_args(&normalized, GENERIC_DIALECT).map_err(|message| TypeParseFailure { normalized_type: normalized.clone(), message, })?; diff --git a/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs b/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs index ba731740dd..cde00841a5 100644 --- a/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs +++ b/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs @@ -550,12 +550,8 @@ fn analyze_compact_query_work( if let (Some(sql), Ok(dialect)) = (diagnostic_sql, dialect) && let Some(validation) = result.validation.take() { - let local_probes = crate::semantic_validation::models::FunctionProbes::default(); - let probes = catalog.map_or(&local_probes, |catalog| catalog.function_probes.as_ref()); result.validation = Some(validation.and_then(|value| { - crate::semantic_validation::main::map_diagnostics::map_diagnostics( - &sql, dialect, value, probes, - ) + crate::semantic_validation::main::map_diagnostics::map_diagnostics(&sql, dialect, value) })); } result diff --git a/crates/sqlbuild-analysis/src/relation_markers/_helpers/mod.rs b/crates/sqlbuild-analysis/src/relation_markers/_helpers/mod.rs new file mode 100644 index 0000000000..b13857eb1c --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/_helpers/mod.rs @@ -0,0 +1 @@ +pub(crate) mod pattern; diff --git a/crates/sqlbuild-analysis/src/relation_markers/_helpers/pattern.rs b/crates/sqlbuild-analysis/src/relation_markers/_helpers/pattern.rs new file mode 100644 index 0000000000..4fa72a78b6 --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/_helpers/pattern.rs @@ -0,0 +1,15 @@ +//! The compiled relation marker call pattern. + +use std::sync::LazyLock; + +use regex::Regex; + +use crate::relation_markers::constants::RELATION_MARKER_PATTERN; + +static RELATION_MARKER: LazyLock> = + LazyLock::new(|| Regex::new(RELATION_MARKER_PATTERN).map_err(|error| error.to_string())); + +/// The compiled relation marker call pattern, or its compile error. +pub(crate) fn relation_marker_pattern() -> Result<&'static Regex, String> { + RELATION_MARKER.as_ref().map_err(Clone::clone) +} diff --git a/crates/sqlbuild-analysis/src/relation_markers/constants.rs b/crates/sqlbuild-analysis/src/relation_markers/constants.rs new file mode 100644 index 0000000000..12db608387 --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/constants.rs @@ -0,0 +1,11 @@ +//! The relation marker call pattern and the names marker calls stand as. + +/// The prefix of the name a marker call stands as while parsed; the call's index follows it. +pub const MARKER_NAME_PREFIX: &str = "__sqb_"; +/// Any relation marker call: `__name("x")`, or `__name("a", "b")` as `__dbt_ref` writes it. +pub(crate) const RELATION_MARKER_PATTERN: &str = + r#"(?i)\b(__[a-z_]+)\(\s*"([^"]+)"\s*(?:,\s*"([^"]+)"\s*)?\)"#; +/// The marker whose two names read as one, joined by `__`. +pub(crate) const DBT_REF_FUNCTION: &str = "__dbt_ref"; +/// The marker of a table function call, which Snowflake reads only inside `TABLE( )`. +pub(crate) const TABLE_FUNCTION_MARKER: &str = "__table_fn"; diff --git a/crates/sqlbuild-analysis/src/relation_markers/main/markers_as_names.rs b/crates/sqlbuild-analysis/src/relation_markers/main/markers_as_names.rs new file mode 100644 index 0000000000..9330c94939 --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/main/markers_as_names.rs @@ -0,0 +1,60 @@ +//! `markers_as_names`: relation marker calls in code as plain names that keep every position. + +use regex::Regex; +use sqlbuild_sqltext::sql_scan::main::dialect_non_code_ranges::dialect_non_code_ranges; +use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; + +use crate::relation_markers::_helpers::pattern::relation_marker_pattern; +use crate::relation_markers::constants::{DBT_REF_FUNCTION, MARKER_NAME_PREFIX}; +use crate::relation_markers::types::MarkerCall; + +/// `sql` with each marker call in code as `__sqb_`, padded so later text keeps its line and column, and the calls by index. +pub fn markers_as_names( + sql: &str, + syntax: &LexicalSyntax, +) -> Result<(String, Vec), String> { + let pattern: &Regex = relation_marker_pattern()?; + let mut protected: Option> = None; + let mut named: String = String::with_capacity(sql.len()); + let mut calls: Vec = Vec::new(); + let mut cursor: usize = 0; + for captures in pattern.captures_iter(sql) { + let (Some(full), Some(function), Some(first)) = + (captures.get(0), captures.get(1), captures.get(2)) + else { + continue; + }; + let ranges = protected.get_or_insert_with(|| dialect_non_code_ranges(sql, syntax)); + let following: usize = ranges.partition_point(|(start, _)| *start <= full.start()); + if following > 0 && full.start() < ranges[following - 1].1 { + continue; + } + let function_name: String = function.as_str().to_ascii_lowercase(); + let referenced_name: String = match (captures.get(3), function_name == DBT_REF_FUNCTION) { + (None, _) => first.as_str().to_owned(), + (Some(second), true) => format!("{}__{}", first.as_str(), second.as_str()), + (Some(_), false) => continue, + }; + named.push_str(&sql[cursor..full.start()]); + named.push_str(&padded_name( + &format!("{MARKER_NAME_PREFIX}{}", calls.len()), + full.as_str(), + )); + cursor = full.end(); + calls.push((function_name, referenced_name)); + } + named.push_str(&sql[cursor..]); + Ok((named, calls)) +} + +/// `name`, then the call's line breaks, then spaces as wide as the call's last line. +fn padded_name(name: &str, call: &str) -> String { + let breaks: usize = call.matches('\n').count(); + let last_line: &str = call.rsplit('\n').next().unwrap_or(call); + let width: usize = if breaks == 0 { + call.chars().count().saturating_sub(name.chars().count()) + } else { + last_line.chars().count() + }; + format!("{name}{}{}", "\n".repeat(breaks), " ".repeat(width)) +} diff --git a/crates/sqlbuild-analysis/src/relation_markers/main/mod.rs b/crates/sqlbuild-analysis/src/relation_markers/main/mod.rs new file mode 100644 index 0000000000..32d7644fcb --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/main/mod.rs @@ -0,0 +1 @@ +pub mod markers_as_names; diff --git a/crates/sqlbuild-analysis/src/relation_markers/mod.rs b/crates/sqlbuild-analysis/src/relation_markers/mod.rs new file mode 100644 index 0000000000..af27b6661d --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/mod.rs @@ -0,0 +1,9 @@ +//! Relation marker calls, such as `__ref("orders")`, read as plain relation names by strict parsers. + +mod _helpers; +pub mod constants; +pub mod main; +pub mod types; + +#[cfg(test)] +mod tests; diff --git a/crates/sqlbuild-analysis/src/relation_markers/tests.rs b/crates/sqlbuild-analysis/src/relation_markers/tests.rs new file mode 100644 index 0000000000..c7c4334980 --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/tests.rs @@ -0,0 +1,4 @@ +#[path = "tests/test_markers_as_names.rs"] +mod test_markers_as_names; +#[path = "tests/test_types.rs"] +mod test_types; diff --git a/crates/sqlbuild-analysis/src/relation_markers/tests/test_markers_as_names.rs b/crates/sqlbuild-analysis/src/relation_markers/tests/test_markers_as_names.rs new file mode 100644 index 0000000000..6df1acbc7a --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/tests/test_markers_as_names.rs @@ -0,0 +1,72 @@ +use sqlbuild_sqltext::sql_scan::main::dialect_lexical_syntax::dialect_lexical_syntax; + +use crate::relation_markers::main::markers_as_names::markers_as_names; +use crate::relation_markers::tests::test_types::MarkersAsNamesTestCase; + +#[test] +fn given_marker_calls_when_reading_them_as_names_then_code_calls_become_names_and_positions_hold() { + let test_cases = [ + MarkersAsNamesTestCase { + description: "each call becomes its indexed name, padded to the call's width", + dialect: "snowflake", + sql: r#"SELECT * FROM __ref("orders") o JOIN __source("raw") r ON TRUE"#, + expected_sql: format!( + "SELECT * FROM __sqb_0{} o JOIN __sqb_1{} r ON TRUE", + " ".repeat(8), + " ".repeat(8) + ), + expected_calls: &[("__ref", "orders"), ("__source", "raw")], + }, + MarkersAsNamesTestCase { + description: "calls in quoted text and comments stay; an upper-case call folds", + dialect: "snowflake", + sql: "SELECT '__ref(\"a\")' AS q -- __ref(\"b\")\nFROM __REF(\"Orders\") // __ref(\"c\")", + expected_sql: format!( + "SELECT '__ref(\"a\")' AS q -- __ref(\"b\")\nFROM __sqb_0{} // __ref(\"c\")", + " ".repeat(8) + ), + expected_calls: &[("__ref", "Orders")], + }, + MarkersAsNamesTestCase { + description: "a call over three lines keeps its line breaks and its last line's width", + dialect: "duckdb", + sql: "FROM __ref(\n \"orders\"\n) AS o", + expected_sql: "FROM __sqb_0\n\n AS o".to_owned(), + expected_calls: &[("__ref", "orders")], + }, + MarkersAsNamesTestCase { + description: "two dbt names join; a non-ASCII name pads by characters, not bytes", + dialect: "postgres", + sql: r#"FROM __dbt_ref("shop", "customers") d JOIN __ref("café") c ON TRUE"#, + expected_sql: format!( + "FROM __sqb_0{} d JOIN __sqb_1{} c ON TRUE", + " ".repeat(23), + " ".repeat(6) + ), + expected_calls: &[("__dbt_ref", "shop__customers"), ("__ref", "café")], + }, + MarkersAsNamesTestCase { + description: "calls with a second name, or a non-literal argument, stay calls", + dialect: "duckdb", + sql: r#"FROM __ref("a", "b") JOIN __table_fn("orders", 2) ON TRUE"#, + expected_sql: r#"FROM __ref("a", "b") JOIN __table_fn("orders", 2) ON TRUE"#.to_owned(), + expected_calls: &[], + }, + ]; + for test_case in test_cases { + let (named, calls) = + markers_as_names(test_case.sql, &dialect_lexical_syntax(test_case.dialect)) + .expect(test_case.description); + let expected_calls: Vec<(String, String)> = test_case + .expected_calls + .iter() + .map(|(function, name)| ((*function).to_owned(), (*name).to_owned())) + .collect(); + assert_eq!( + (named, calls), + (test_case.expected_sql, expected_calls), + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlbuild-analysis/src/relation_markers/tests/test_types.rs b/crates/sqlbuild-analysis/src/relation_markers/tests/test_types.rs new file mode 100644 index 0000000000..b92a6d74fa --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/tests/test_types.rs @@ -0,0 +1,10 @@ +//! Test case types for relation markers read as names. + +/// SQL in one dialect, the SQL its markers read as, and the calls they stand for. +pub(crate) struct MarkersAsNamesTestCase { + pub(crate) description: &'static str, + pub(crate) dialect: &'static str, + pub(crate) sql: &'static str, + pub(crate) expected_sql: String, + pub(crate) expected_calls: &'static [(&'static str, &'static str)], +} diff --git a/crates/sqlbuild-analysis/src/relation_markers/types.rs b/crates/sqlbuild-analysis/src/relation_markers/types.rs new file mode 100644 index 0000000000..9b90a22d2a --- /dev/null +++ b/crates/sqlbuild-analysis/src/relation_markers/types.rs @@ -0,0 +1,4 @@ +//! Shapes of relation marker calls. + +/// A marker call's lower-case function, such as `__ref`, and the name it references. +pub type MarkerCall = (String, String); diff --git a/crates/sqlbuild-analysis/src/semantic_checks/tests/test_metadata.rs b/crates/sqlbuild-analysis/src/semantic_checks/tests/test_metadata.rs index 55c2ed010d..7f5d8d5bb3 100644 --- a/crates/sqlbuild-analysis/src/semantic_checks/tests/test_metadata.rs +++ b/crates/sqlbuild-analysis/src/semantic_checks/tests/test_metadata.rs @@ -17,8 +17,8 @@ fn given_project_metadata_when_checking_then_reports_python_errors() { expected_outcome: Ok(expected_checked_outcome()), }, MetadataTestCase { - description: "a dialect the all-dialects parser build added", - dialect: "mysql", + description: "a shipped dialect without a native type parser", + dialect: "tsql", mart_shape: MART_SHAPE, expected_outcome: Ok(expected_checked_outcome()), }, diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs index 32fd8e8198..9ac0e4b46d 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs @@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, PoisonError}; use crate::semantic_validation::models::{ - CatalogInput, Columns, CompactAnalysisJob, DiagnosticRow, FunctionProbes, ProjectCatalog, + CatalogInput, Columns, CompactAnalysisJob, DiagnosticRow, ProjectCatalog, }; use crate::semantic_validation::types::{ BindingRequest, NormalizationRequest, PreparedCompactAnalysis, Relations, @@ -160,7 +160,6 @@ impl ProjectCatalog { tables: HashMap::new(), overrides: Vec::new(), analysis_tables: HashMap::new(), - function_probes: Arc::new(FunctionProbes::default()), analysis_pool: Arc::default(), running_analyses: Arc::default(), }; @@ -176,15 +175,9 @@ impl ProjectCatalog { pub fn with_relations(&self, relations: Relations) -> Self { let mut catalog = Self { - dialect: self.dialect, - options: self.options.clone(), - quoted_ignore_case: self.quoted_ignore_case, tables: self.tables.clone(), - overrides: Vec::new(), analysis_tables: self.analysis_tables.clone(), - function_probes: Arc::new(FunctionProbes::default()), - analysis_pool: Arc::clone(&self.analysis_pool), - running_analyses: Arc::clone(&self.running_analyses), + ..self.analysis_view() }; catalog.update_relations(relations); catalog @@ -285,12 +278,7 @@ impl ProjectCatalog { .map(|(sql, references, overrides)| { let schema = self.schema(&references, overrides)?; let result = self.validate(&sql, &schema)?; - let result = diagnostics::map_diagnostics( - &sql, - self.dialect, - result, - &self.function_probes, - )?; + let result = diagnostics::map_diagnostics(&sql, self.dialect, result)?; Ok(result.errors.into_iter().map(diagnostic_row).collect()) }) .collect::, String>>() @@ -356,7 +344,6 @@ impl ProjectCatalog { tables: HashMap::new(), overrides: Vec::new(), analysis_tables: HashMap::new(), - function_probes: Arc::clone(&self.function_probes), analysis_pool: Arc::clone(&self.analysis_pool), running_analyses: Arc::clone(&self.running_analyses), } @@ -388,7 +375,6 @@ impl ProjectCatalog { tables: HashMap::new(), overrides: Vec::new(), analysis_tables: HashMap::new(), - function_probes: Arc::new(FunctionProbes::default()), analysis_pool: Arc::default(), running_analyses: Arc::default(), } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs index b198ae8a54..2b98a1f64e 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs @@ -6,7 +6,6 @@ use crate::semantic_validation::_helpers::sqlite_temporal_casts; use crate::semantic_validation::_helpers::truth_tests; use crate::semantic_validation::_helpers::type_locations::locate_type_findings; use crate::semantic_validation::models::DiagnosticRow; -use crate::semantic_validation::models::FunctionProbes; use polyglot_sql::expressions::Select; use polyglot_sql::{ Dialect, DialectType, Expression, ExpressionWalk, ValidationError, ValidationResult, @@ -149,7 +148,6 @@ pub(crate) fn map_diagnostics( sql: &str, dialect: DialectType, result: ValidationResult, - probes: &FunctionProbes, ) -> Result { let mut context = DiagnosticContext { sql, @@ -164,9 +162,7 @@ pub(crate) fn map_diagnostics( errors.push(error); } } - errors.extend(function_names::unsupported_function_errors( - sql, dialect, probes, - )); + errors.extend(function_names::unsupported_function_errors(sql, dialect)); errors.extend(truth_tests::unsupported_truth_test_errors(sql, dialect)); errors.extend(sqlite_temporal_casts::sqlite_temporal_cast_warnings( sql, dialect, @@ -418,7 +414,6 @@ pub(crate) fn binding_diagnostics( valid: false, errors, }, - &FunctionProbes::default(), )?; Ok(result.errors.into_iter().map(diagnostic_row).collect()) } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs index ca0337945e..0c697b9487 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs @@ -1,28 +1,17 @@ //! Built-in function spellings checked against the spellings the target dialect accepts. -use crate::semantic_validation::models::FunctionProbes; -use crate::semantic_validation::types::ProbeKey; -use polyglot_sql::{Dialect, DialectType, Expression, ValidationError}; -use polyglot_sql_function_catalogs::{CatalogSink, FunctionNameCase, FunctionSignature}; +use polyglot_sql::{DialectType, ValidationError}; use rustc_hash::{FxHashMap, FxHashSet}; use sqlbuild_sqltext::sql_scan::main::comment_end::comment_end; use sqlbuild_sqltext::sql_scan::main::non_code_end::non_code_end; use sqlbuild_sqltext::sql_scan::models::{QuotePolicy, Unclosed}; -use std::mem::Discriminant; -use std::sync::{LazyLock, OnceLock, PoisonError}; +use std::sync::LazyLock; const UNKNOWN_FUNCTION: &str = "B101"; -const SNOWFLAKE_FUNCTIONS: &str = include_str!("snowflake_functions.txt"); -const DUCKDB_CATALOG: &str = "duckdb"; -const PROBE_PREFIX: &str = "SELECT "; +const FUNCTION_SPELLINGS: &str = include_str!("function_spellings.txt"); +const SNOWFLAKE_DIALECT: &str = "snowflake"; +const DUCKDB_DIALECT: &str = "duckdb"; const TYPE_POSITION_KEYWORD: &str = "AS"; -const PROBE_ARITIES: usize = 9; -/// Keywords that may precede a parenthesis without being a function call. -const NON_CALL_KEYWORDS: &str = "ALL AND ANY AS ASOF AT BEFORE BETWEEN BY CASE CHANGES CONNECT CUBE \ - DEFINE DISTINCT DISTINCTROW ELSE END EXCEPT EXCLUDE EXISTS FILTER FROM HAVING ILIKE IN \ - INTERSECT INTERVAL IS JOIN LATERAL LIKE MATCH_CONDITION MATCH_RECOGNIZE MEASURES MINUS NOT ON \ - OR OVER PARTITION PATTERN PERCENT PIVOT PRIOR QUALIFY RANGE RENAME ROLLUP ROW ROWS SAMPLE \ - SELECT SETS SOME TABLE TABLESAMPLE THEN UNION UNPIVOT USING VALUES WHEN WHERE WITH WITHIN"; /// Words after which an expression, and so a function call, may start. const EXPRESSION_KEYWORDS: &str = "ALL AND ANY BETWEEN BY CASE DISTINCT ELSE ELSEIF ESCAPE HAVING \ @@ -37,10 +26,9 @@ const NOT_KEYWORD: &str = "NOT"; const MATERIALIZED_KEYWORD: &str = "MATERIALIZED"; const DISTINCT_KEYWORD: &str = "DISTINCT"; -type SpellingIndex = FxHashMap>; +/// Where one rejected spelling applies: the dialect, the argument count and the accepted spelling. +type SpellingRow = (&'static str, usize, &'static str); -static KEYWORDS: LazyLock> = - LazyLock::new(|| NON_CALL_KEYWORDS.split_whitespace().collect()); static EXPRESSION_STARTS: LazyLock> = LazyLock::new(|| EXPRESSION_KEYWORDS.split_whitespace().collect()); static CLAUSE_KEYWORDS: LazyLock> = @@ -49,17 +37,8 @@ static ROOT_FRAME: Frame = Frame { select_seen: false, clause: Clause::Other, }; -static SNOWFLAKE: LazyLock> = - LazyLock::new(|| accepted_names(SNOWFLAKE_FUNCTIONS)); -static DUCKDB: LazyLock> = LazyLock::new(|| { - let mut sink: CatalogNames = CatalogNames::default(); - polyglot_sql_function_catalogs::register_enabled_catalogs(&mut sink); - sink.names -}); -static SNOWFLAKE_INDEX: [OnceLock; PROBE_ARITIES] = - [const { OnceLock::new() }; PROBE_ARITIES]; -static DUCKDB_INDEX: [OnceLock; PROBE_ARITIES] = - [const { OnceLock::new() }; PROBE_ARITIES]; +static SPELLINGS: LazyLock>> = + LazyLock::new(|| spellings(FUNCTION_SPELLINGS)); /// One call site whose authored built-in spelling the dialect does not accept. #[derive(Debug, PartialEq, Eq)] @@ -95,21 +74,6 @@ struct Scan { path_colon: bool, } -/// How the parser represents one probe call. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum Signature { - /// A generic function node with this upper-case name. - Named(String), - /// A typed built-in node, identified by its expression variant. - Typed(Discriminant), -} - -/// Upper-case DuckDB names from the SQL library's introspected function catalogue. -#[derive(Default)] -struct CatalogNames { - names: FxHashSet, -} - /// Incremental byte-to-character position, so locating every call stays linear in the SQL. pub(crate) struct Cursor { byte: usize, @@ -118,29 +82,6 @@ pub(crate) struct Cursor { pub(crate) column: usize, } -impl CatalogSink for CatalogNames { - fn set_dialect_name_case(&mut self, _dialect: &'static str, _name_case: FunctionNameCase) {} - - fn set_function_name_case( - &mut self, - _dialect: &'static str, - _function_name: &str, - _name_case: FunctionNameCase, - ) { - } - - fn register( - &mut self, - dialect: &'static str, - function_name: &str, - _signatures: Vec, - ) { - if dialect == DUCKDB_CATALOG && is_identifier(function_name) { - self.names.insert(function_name.to_ascii_uppercase()); - } - } -} - impl Cursor { pub(crate) fn new() -> Self { Self { @@ -163,79 +104,84 @@ impl Cursor { } self.byte = byte; } + + /// `error` located at the byte range `start..end` of `sql`, with its character span. + pub(crate) fn locate( + &mut self, + sql: &str, + (start, end): (usize, usize), + error: ValidationError, + ) -> ValidationError { + self.advance(sql, start); + let first: usize = self.characters; + let last: usize = first + sql[start..end].chars().count(); + error + .with_location(self.line, self.column) + .with_span(Some(first), Some(last)) + } } -impl FunctionProbes { - /// Return the accepted spelling for an unaccepted call-site name the parser treats as built-in. - fn suggestion(&self, dialect: DialectType, upper: &str, arity: usize) -> Option { - let key: ProbeKey = (dialect, upper.to_owned(), arity.min(PROBE_ARITIES - 1)); - if let Some(cached) = self - .suggestions - .lock() - .unwrap_or_else(PoisonError::into_inner) - .get(&key) +/// Read `dialect SPELLING arguments ACCEPTED` rows; `#` starts a comment. +fn spellings(source: &'static str) -> FxHashMap<&'static str, Vec> { + let mut rows: FxHashMap<&'static str, Vec> = FxHashMap::default(); + for line in source.lines().map(str::trim) { + let mut fields = line.split_whitespace(); + if let (Some(dialect), Some(spelling), Some(arity), Some(accepted)) = + (fields.next(), fields.next(), fields.next(), fields.next()) + && !dialect.starts_with('#') + && let Ok(arity) = arity.parse::() { - return cached.clone(); + rows.entry(spelling) + .or_default() + .push((dialect, arity, accepted)); } - let found: Option = closest_spelling(dialect, upper, key.2); - self.suggestions - .lock() - .unwrap_or_else(PoisonError::into_inner) - .insert(key, found.clone()); - found } + rows } -fn accepted_names(source: &str) -> FxHashSet { - source - .lines() - .map(str::trim) - .filter(|line| !line.is_empty() && !line.starts_with('#')) - .map(str::to_ascii_uppercase) - .collect() +/// Return the accepted spelling for `upper` called with `arity` arguments in `dialect`. +fn accepted_spelling(dialect: &str, upper: &str, arity: usize) -> Option<&'static str> { + SPELLINGS + .get(upper)? + .iter() + .find_map(|(row_dialect, row_arity, accepted)| { + (*row_dialect == dialect && *row_arity == arity).then_some(*accepted) + }) } -fn accepted(dialect: DialectType) -> Option<&'static FxHashSet> { +/// The analysis dialect name the spelling table is keyed on, for the legacy validation path. +fn dialect_name(dialect: DialectType) -> Option<&'static str> { match dialect { - DialectType::Snowflake => Some(&SNOWFLAKE), - DialectType::DuckDB => Some(&DUCKDB), + DialectType::Snowflake => Some(SNOWFLAKE_DIALECT), + DialectType::DuckDB => Some(DUCKDB_DIALECT), _ => None, } } /// Return located B101 diagnostics, with character spans, for unsupported built-in spellings. -pub(crate) fn unsupported_function_errors( - sql: &str, - dialect: DialectType, - probes: &FunctionProbes, -) -> Vec { +pub(crate) fn unsupported_function_errors(sql: &str, dialect: DialectType) -> Vec { let mut cursor: Cursor = Cursor::new(); - unsupported_calls(sql, dialect, probes) + let Some(name) = dialect_name(dialect) else { + return Vec::new(); + }; + unsupported_calls(sql, name) .into_iter() .map(|call| { let message: String = format!( "Unknown function '{}' for dialect {dialect:?}; did you mean {}?", call.name, call.suggestion ); - cursor.advance(sql, call.start); - let start: usize = cursor.characters; - let end: usize = start + sql[call.start..call.end].chars().count(); - ValidationError::error(message, UNKNOWN_FUNCTION) - .with_location(cursor.line, cursor.column) - .with_span(Some(start), Some(end)) + cursor.locate( + sql, + (call.start, call.end), + ValidationError::error(message, UNKNOWN_FUNCTION), + ) }) .collect() } -/// Return call sites in `sql` whose built-in spelling `dialect` does not accept. -pub(crate) fn unsupported_calls( - sql: &str, - dialect: DialectType, - probes: &FunctionProbes, -) -> Vec { - let Some(names) = accepted(dialect) else { - return Vec::new(); - }; +/// Return call sites in `sql` whose built-in spelling the analysis `dialect` does not accept. +pub(crate) fn unsupported_calls(sql: &str, dialect: &str) -> Vec { let bytes: &[u8] = sql.as_bytes(); let mut calls: Vec = Vec::new(); let mut scan: Scan = Scan::new(); @@ -277,18 +223,15 @@ pub(crate) fn unsupported_calls( let open: usize = skip_trivia(bytes, index); if bytes.get(open) == Some(&b'(') && scan.call_position() - && !name.starts_with("__") - && !names.contains(&upper) - && !KEYWORDS.contains(upper.as_str()) && let Some((arity, close)) = arguments(bytes, open) && !opens_cte_body(bytes, close + 1) - && let Some(suggestion) = probes.suggestion(dialect, &upper, arity) + && let Some(suggestion) = accepted_spelling(dialect, &upper, arity) { calls.push(UnsupportedCall { name: name.to_owned(), start, end: index, - suggestion, + suggestion: suggestion.to_owned(), }); } scan.word(upper); @@ -416,13 +359,6 @@ pub(crate) fn is_word_byte(byte: u8) -> bool { byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'$' } -fn is_identifier(name: &str) -> bool { - name.bytes() - .next() - .is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_') - && name.bytes().all(is_word_byte) -} - /// Return the top-level argument count and closing index of the call opening at `open`. fn arguments(sql: &[u8], open: usize) -> Option<(usize, usize)> { let mut depth: usize = 0; @@ -463,76 +399,3 @@ fn arguments(sql: &[u8], open: usize) -> Option<(usize, usize)> { } None } - -/// Return the closest accepted spelling the parser maps to the same built-in node as `upper`. -fn closest_spelling(dialect: DialectType, upper: &str, arity: usize) -> Option { - let signature: Signature = parsed_signature(dialect, upper, arity)?; - if signature == Signature::Named(upper.to_owned()) { - return None; - } - spelling_index(dialect, arity)? - .get(&signature)? - .iter() - .min_by_key(|accepted| (edit_distance(upper, accepted), (*accepted).clone())) - .cloned() -} - -fn spelling_index(dialect: DialectType, arity: usize) -> Option<&'static SpellingIndex> { - let (names, indexes) = match dialect { - DialectType::Snowflake => (&*SNOWFLAKE, &SNOWFLAKE_INDEX), - DialectType::DuckDB => (&*DUCKDB, &DUCKDB_INDEX), - _ => return None, - }; - Some(indexes.get(arity)?.get_or_init(|| { - let mut index: SpellingIndex = FxHashMap::default(); - for name in names { - if let Some(signature) = parsed_signature(dialect, name, arity) { - index.entry(signature).or_default().push(name.clone()); - } - } - index - })) -} - -fn parsed_signature(dialect: DialectType, upper: &str, arity: usize) -> Option { - let arguments: Vec = (1..=arity).map(|index| format!("a{index}")).collect(); - let sql: String = format!("{PROBE_PREFIX}{upper}({})", arguments.join(", ")); - let Ok(statements) = Dialect::get(dialect).parse(&sql) else { - return None; - }; - let [Expression::Select(select)] = statements.as_slice() else { - return None; - }; - let [expression] = select.expressions.as_slice() else { - return None; - }; - match expression { - Expression::Function(function) => { - Some(Signature::Named(function.name.to_ascii_uppercase())) - } - Expression::AggregateFunction(function) => { - Some(Signature::Named(function.name.to_ascii_uppercase())) - } - Expression::Column(_) - | Expression::Identifier(_) - | Expression::Tuple(_) - | Expression::Paren(_) => None, - other => Some(Signature::Typed(std::mem::discriminant(other))), - } -} - -fn edit_distance(left: &str, right: &str) -> usize { - let right: Vec = right.chars().collect(); - let mut previous: Vec = (0..=right.len()).collect(); - for (row, left_char) in left.chars().enumerate() { - let mut current: Vec = vec![row + 1; right.len() + 1]; - for (column, right_char) in right.iter().enumerate() { - let substitution: usize = previous[column] + usize::from(left_char != *right_char); - current[column + 1] = substitution - .min(previous[column + 1] + 1) - .min(current[column] + 1); - } - previous = current; - } - previous[right.len()] -} diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt new file mode 100644 index 0000000000..06b81043cd --- /dev/null +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt @@ -0,0 +1,48 @@ +# Built-in spellings SQLBuild's compile-time name check (B101) rejects, with the spelling the +# dialect accepts for the same function. A call is rejected only when its spelling and argument +# count appear here, so user-defined and unknown functions are never rejected. +# +# Columns: analysis dialect, upper-case spelling, argument count, accepted spelling. +# Every Snowflake spelling is absent from Snowflake's function reference; every DuckDB spelling +# is unknown to DuckDB 2.0.0 with its core extensions. Add a row only with that evidence. +snowflake CEILING 1 CEIL +snowflake CEILING 2 CEIL +snowflake CHARACTER_LENGTH 1 LENGTH +snowflake CHAR_LENGTH 1 LENGTH +snowflake COUNTIF 1 COUNT_IF +snowflake DATEPART 2 DATE_PART +snowflake DAY_OF_MONTH 1 DAYOFMONTH +snowflake DAY_OF_WEEK 1 DAYOFWEEK +snowflake DAY_OF_YEAR 1 DAYOFYEAR +snowflake ENDS_WITH 2 ENDSWITH +snowflake IF 2 IFF +snowflake IF 3 IFF +snowflake IIF 2 IFF +snowflake IIF 3 IFF +snowflake INSTR 2 POSITION +snowflake INSTR 3 POSITION +snowflake LCASE 1 LOWER +snowflake LOCATE 2 POSITION +snowflake LOCATE 3 POSITION +snowflake RAND 1 RANDOM +snowflake STARTS_WITH 2 STARTSWITH +snowflake STRPOS 2 POSITION +snowflake STRPOS 3 POSITION +snowflake TEXTLEN 1 LEN +snowflake TOTYPENAME 1 TYPEOF +snowflake UCASE 1 UPPER +snowflake WEEK_OF_YEAR 1 WEEKOFYEAR +duckdb DAY_OF_MONTH 1 DAYOFMONTH +duckdb DAY_OF_WEEK 1 DAYOFWEEK +duckdb DAY_OF_YEAR 1 DAYOFYEAR +duckdb ENDSWITH 2 ENDS_WITH +duckdb EVERY 1 BOOL_AND +duckdb LOCATE 2 INSTR +duckdb LOCATE 3 INSTR +duckdb LOGICAL_AND 1 BOOL_AND +duckdb LOGICAL_OR 1 BOOL_OR +duckdb RAND 1 RANDOM +duckdb STARTSWITH 2 STARTS_WITH +duckdb TEXTLEN 1 LEN +duckdb TOTYPENAME 1 TYPEOF +duckdb WEEK_OF_YEAR 1 WEEKOFYEAR diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt deleted file mode 100644 index 6147565f61..0000000000 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt +++ /dev/null @@ -1,993 +0,0 @@ -# Snowflake built-in function spellings accepted by SQLBuild's compile-time name check (B101). -# -# Source: Snowflake's public "All functions (alphabetical)" reference, including every listed -# alias (for example SUBSTR and SUBSTRING, DATEDIFF, TIMEDIFF and TIMESTAMPDIFF, TRY_TO_DECIMAL, -# TRY_TO_NUMBER and TRY_TO_NUMERIC, STARTSWITH, ENDSWITH, CONTAINS, IFF), with wildcard families -# such as TO_TIMESTAMP_* and YEAR*/DAY*/WEEK* expanded, plus SYS_CONNECT_BY_PATH from the -# CONNECT BY reference. Names are case-insensitive; one name per line; `#` starts a comment. -# -# A spelling absent from this list is rejected only when the SQL parser maps it to the same -# built-in node as a listed spelling, so user-defined and unknown functions are never rejected. -# Add a missing Snowflake built-in here rather than suppressing the diagnostic. -ABS -ACCEPTED_VALUES -ACOS -ACOSH -ADD_MONTHS -AGENT_RUN -AGG -AI_AGG -AI_CLASSIFY -AI_COMPLETE -AI_COUNT_TOKENS -AI_EMBED -AI_EXTRACT -AI_FILTER -AI_MULTI_EMBED -AI_PARSE_DOCUMENT -AI_REDACT -AI_SENTIMENT -AI_SIMILARITY -AI_SUMMARIZE -AI_SUMMARIZE_AGG -AI_TRANSCRIBE -AI_TRANSLATE -ALERT_HISTORY -ALL_USER_NAMES -ANY_VALUE -APPLICATION_CALLBACK_HISTORY -APPLICATION_CONFIGURATION_VALUE_HISTORY -APPLICATION_JSON -APPLICATION_SPECIFICATION_STATUS_HISTORY -APPROXIMATE_JACCARD_INDEX -APPROXIMATE_SIMILARITY -APPROX_COUNT_DISTINCT -APPROX_PERCENTILE -APPROX_PERCENTILE_ACCUMULATE -APPROX_PERCENTILE_COMBINE -APPROX_PERCENTILE_ESTIMATE -APPROX_TOP_K -APPROX_TOP_K_ACCUMULATE -APPROX_TOP_K_COMBINE -APPROX_TOP_K_ESTIMATE -ARRAYS_OVERLAP -ARRAYS_TO_OBJECT -ARRAYS_ZIP -ARRAY_AGG -ARRAY_APPEND -ARRAY_CAT -ARRAY_COMPACT -ARRAY_CONSTRUCT -ARRAY_CONSTRUCT_COMPACT -ARRAY_CONSTRUCT_STRUCTURED -ARRAY_CONTAINS -ARRAY_DISTINCT -ARRAY_EXCEPT -ARRAY_FLATTEN -ARRAY_GENERATE_RANGE -ARRAY_INSERT -ARRAY_INTERSECTION -ARRAY_MAX -ARRAY_MIN -ARRAY_POSITION -ARRAY_PREPEND -ARRAY_REMOVE -ARRAY_REMOVE_AT -ARRAY_REVERSE -ARRAY_SIZE -ARRAY_SLICE -ARRAY_SORT -ARRAY_TO_STRING -ARRAY_UNION_AGG -ARRAY_UNIQUE_AGG -ASCII -ASIN -ASINH -AS_ARRAY -AS_BINARY -AS_BOOLEAN -AS_CHAR -AS_DATE -AS_DECIMAL -AS_DOUBLE -AS_INTEGER -AS_NUMBER -AS_OBJECT -AS_REAL -AS_TIME -AS_TIMESTAMP_LTZ -AS_TIMESTAMP_NTZ -AS_TIMESTAMP_TZ -AS_VARCHAR -ATAN -ATAN2 -ATANH -AUTOMATIC_CLUSTERING_HISTORY -AUTO_REFRESH_REGISTRATION_HISTORY -AVAILABLE_LISTINGS -AVAILABLE_LISTING_REFRESH_HISTORY -AVG -BASE64_DECODE_BINARY -BASE64_DECODE_STRING -BASE64_ENCODE -BETWEEN -BIND_VALUES -BITAND -BITAND_AGG -BITMAP_ABSOLUTE_POSITION -BITMAP_AND -BITMAP_AND_AGG -BITMAP_BIT_POSITION -BITMAP_BUCKET_NUMBER -BITMAP_CONSTRUCT_AGG -BITMAP_COUNT -BITMAP_OR -BITMAP_OR_AGG -BITMAP_TO_ARRAY -BITNOT -BITOR -BITOR_AGG -BITSHIFTLEFT -BITSHIFTRIGHT -BITXOR -BITXOR_AGG -BIT_LENGTH -BLANK_COUNT -BLANK_PERCENT -BOOLAND -BOOLAND_AGG -BOOLNOT -BOOLOR -BOOLOR_AGG -BOOLXOR -BOOLXOR_AGG -BUILD_SCOPED_FILE_URL -BUILD_STAGE_FILE_URL -CASE -CAST -CBRT -CEIL -CHAR -CHARINDEX -CHECK_JSON -CHECK_XML -CHR -CLASSIFY_TEXT -COALESCE -COLLATE -COLLATION -COMPLETE -COMPLETE_TASK_GRAPHS -COMPRESS -CONCAT -CONCAT_WS -CONDITIONAL_CHANGE_EVENT -CONDITIONAL_TRUE_EVENT -CONTAINS -CONVERT_TIMEZONE -COPY_HISTORY -CORR -CORTEX_SEARCH_BATCH -CORTEX_SEARCH_DATA_SCAN -CORTEX_SEARCH_REFRESH_HISTORY -COS -COSH -COT -COUNT -COUNT_IF -COUNT_TOKENS -COVAR_POP -COVAR_SAMP -CUME_DIST -CUMULATIVE_PRIVACY_LOSSES -CURRENT_ACCOUNT -CURRENT_ACCOUNT_NAME -CURRENT_AVAILABLE_ROLES -CURRENT_CLIENT -CURRENT_DATABASE -CURRENT_DATE -CURRENT_IP_ADDRESS -CURRENT_ORGANIZATION_NAME -CURRENT_ORGANIZATION_USER -CURRENT_REGION -CURRENT_ROLE -CURRENT_ROLE_TYPE -CURRENT_SCHEMA -CURRENT_SCHEMAS -CURRENT_SECONDARY_ROLES -CURRENT_SESSION -CURRENT_STATEMENT -CURRENT_TASK_GRAPHS -CURRENT_TIME -CURRENT_TIMESTAMP -CURRENT_TRANSACTION -CURRENT_USER -CURRENT_VERSION -CURRENT_WAREHOUSE -DATABASE_REFRESH_HISTORY -DATABASE_REFRESH_PROGRESS -DATABASE_REFRESH_PROGRESS_BY_JOB -DATABASE_REPLICATION_USAGE_HISTORY -DATABASE_STORAGE_USAGE_HISTORY -DATASKETCHES_HLL -DATASKETCHES_HLL_ACCUMULATE -DATASKETCHES_HLL_COMBINE -DATASKETCHES_HLL_ESTIMATE -DATA_AGENT_RUN -DATA_METRIC_FUNCTION_EXPECTATIONS -DATA_METRIC_FUNCTION_REFERENCES -DATA_METRIC_SCHEDULED_TIME -DATA_QUALITY_MONITORING_EXPECTATION_STATUS -DATA_QUALITY_MONITORING_RESULTS -DATA_TRANSFER_HISTORY -DATE -DATEADD -DATEDIFF -DATE_FROM_PARTS -DATE_PART -DATE_TRUNC -DAY -DAYNAME -DAYOFMONTH -DAYOFWEEK -DAYOFWEEKISO -DAYOFYEAR -DBT_PROJECT_EXECUTION_HISTORY -DECODE -DECOMPRESS_BINARY -DECOMPRESS_STRING -DECRYPT -DECRYPT_RAW -DEGREES -DENSE_RANK -DIV0 -DIV0NULL -DP_INTERVAL_HIGH -DP_INTERVAL_LOW -DUPLICATE_COUNT -DYNAMIC_TABLES -DYNAMIC_TABLE_GRAPH_HISTORY -DYNAMIC_TABLE_REFRESH_HISTORY -EDITDISTANCE -EMAIL_INTEGRATION_CONFIG -EMBED_TEXT_1024 -EMBED_TEXT_768 -ENCRYPT -ENCRYPT_RAW -ENDSWITH -ENTITY_SENTIMENT -EQUAL_NULL -ESTIMATE_REMAINING_DP_AGGREGATES -EXECUTE_AI_EVALUATION -EXP -EXPLAIN_JSON -EXTERNAL_FUNCTIONS_HISTORY -EXTERNAL_TABLE_FILES -EXTERNAL_TABLE_FILE_REGISTRATION_HISTORY -EXTRACT -EXTRACT_ANSWER -EXTRACT_SEMANTIC_CATEGORIES -EXTREME_OUTLIER_COUNT -EXTREME_OUTLIER_IQR_COUNT -EXTREME_OUTLIER_IQR_PERCENT -EXTREME_OUTLIER_PERCENT -EXTREME_OUTLIER_ZSCORE_COUNT -EXTREME_OUTLIER_ZSCORE_PERCENT -FACTORIAL -FILTER -FINETUNE -FIRST_VALUE -FLATTEN -FLOOR -FL_GET_CONTENT_TYPE -FL_GET_ETAG -FL_GET_FILE_TYPE -FL_GET_LAST_MODIFIED -FL_GET_RELATIVE_PATH -FL_GET_SCOPED_FILE_URL -FL_GET_SIZE -FL_GET_STAGE -FL_GET_STAGE_FILE_URL -FL_IS_AUDIO -FL_IS_COMPRESSED -FL_IS_DOCUMENT -FL_IS_IMAGE -FL_IS_VIDEO -FRESHNESS -GENERATE_COLUMN_DESCRIPTION -GENERATE_POSTGRES_ACCESS_TOKEN_FOR_USER -GENERATOR -GET -GETBIT -GETDATE -GETVARIABLE -GET_ABSOLUTE_PATH -GET_AI_EVALUATION_DATA -GET_AI_OBSERVABILITY_LOGS -GET_AI_RECORD_TRACE -GET_ANACONDA_PACKAGES_REPODATA -GET_CONDITION_QUERY_UUID -GET_CONFIGURATION_VALUE -GET_CONTACTS -GET_DDL -GET_IGNORE_CASE -GET_JOB_HISTORY -GET_LINEAGE -GET_OBJECT_REFERENCES -GET_PATH -GET_PRESIGNED_URL -GET_PYTHON_PROFILER_OUTPUT -GET_QUERY_OPERATOR_STATS -GET_RELATIVE_PATH -GET_STAGE_LOCATION -GREATEST -GREATEST_IGNORE_NULLS -GROUPING -GROUPING_ID -H3_CELL_TO_BOUNDARY -H3_CELL_TO_CHILDREN -H3_CELL_TO_CHILDREN_STRING -H3_CELL_TO_PARENT -H3_CELL_TO_POINT -H3_COMPACT_CELLS -H3_COMPACT_CELLS_STRINGS -H3_COVERAGE -H3_COVERAGE_STRINGS -H3_GET_RESOLUTION -H3_GRID_DISK -H3_GRID_DISTANCE -H3_GRID_PATH -H3_INT_TO_STRING -H3_IS_PENTAGON -H3_IS_VALID_CELL -H3_LATLNG_TO_CELL -H3_LATLNG_TO_CELL_STRING -H3_POINT_TO_CELL -H3_POINT_TO_CELL_STRING -H3_POLYGON_TO_CELLS -H3_POLYGON_TO_CELLS_STRINGS -H3_STRING_TO_INT -H3_TRY_COVERAGE -H3_TRY_COVERAGE_STRINGS -H3_TRY_GRID_DISTANCE -H3_TRY_GRID_PATH -H3_TRY_POLYGON_TO_CELLS -H3_TRY_POLYGON_TO_CELLS_STRINGS -H3_UNCOMPACT_CELLS -H3_UNCOMPACT_CELLS_STRINGS -HASH -HASH_AGG -HAVERSINE -HEX_DECODE_BINARY -HEX_DECODE_STRING -HEX_ENCODE -HLL -HLL_ACCUMULATE -HLL_COMBINE -HLL_ESTIMATE -HLL_EXPORT -HLL_IMPORT -HOUR -ICEBERG_TABLE_FILES -ICEBERG_TABLE_SNAPSHOT_REFRESH_HISTORY -IFF -IFNULL -ILIKE -IN -INFER_SCHEMA -INITCAP -INSERT -INTEGRATION -INTERPOLATE_BFILL -INTERPOLATE_FFILL -INTERPOLATE_LINEAR -INVOKER_ROLE -INVOKER_SHARE -IS_APPLICATION_ROLE_ACTIVATED -IS_APPLICATION_ROLE_IN_SESSION -IS_ARRAY -IS_BINARY -IS_BOOLEAN -IS_CHAR -IS_CONFIGURATION_SET -IS_DATABASE_ROLE_ACTIVATED -IS_DATABASE_ROLE_IN_SESSION -IS_DATE -IS_DATE_VALUE -IS_DECIMAL -IS_DOUBLE -IS_GRANTED_TO_INVOKER_ROLE -IS_GROUP_ACTIVATED -IS_GROUP_IMPORTED -IS_INSTANCE_ROLE_IN_SESSION -IS_INTEGER -IS_NULL_VALUE -IS_OBJECT -IS_ORGANIZATION_USER -IS_ORGANIZATION_USER_GROUP -IS_ORGANIZATION_USER_GROUP_IN_SESSION -IS_REAL -IS_ROLE_ACTIVATED -IS_ROLE_IN_SESSION -IS_TIME -IS_TIMESTAMP_LTZ -IS_TIMESTAMP_NTZ -IS_TIMESTAMP_TZ -IS_USER_IMPORTED -IS_VARCHAR -JAROWINKLER_SIMILARITY -JSON_EXTRACT_PATH_TEXT -KURTOSIS -LAG -LAST_DAY -LAST_QUERY_ID -LAST_SUCCESSFUL_SCHEDULED_TIME -LAST_TRANSACTION -LAST_VALUE -LEAD -LEAST -LEAST_IGNORE_NULLS -LEFT -LEN -LENGTH -LIKE -LISTAGG -LISTING_REFRESH_HISTORY -LN -LOCALTIME -LOCALTIMESTAMP -LOG -LOGIN_HISTORY -LOGIN_HISTORY_BY_USER -LOWER -LPAD -LTRIM -MAP_CAT -MAP_CONSTRUCT -MAP_CONTAINS_KEY -MAP_DELETE -MAP_ENTRIES -MAP_INSERT -MAP_KEYS -MAP_PICK -MAP_SIZE -MATERIALIZED_VIEW_REFRESH_HISTORY -MAX -MAX_BY -MD5 -MD5_BINARY -MD5_HEX -MD5_NUMBER_LOWER64 -MD5_NUMBER_UPPER64 -MEDIAN -MIN -MINHASH -MINHASH_COMBINE -MINUTE -MIN_BY -MOD -MODE -MODEL_MONITOR_DRIFT_METRIC -MODEL_MONITOR_PERFORMANCE_METRIC -MODEL_MONITOR_STAT_METRIC -MONTH -MONTHNAME -MONTHS_BETWEEN -NETWORK_RULE_REFERENCES -NEXT_DAY -NORMAL -NOTIFICATION_HISTORY -NTH_VALUE -NTILE -NULLIF -NULLIFZERO -NULL_COUNT -NULL_PERCENT -NVL -NVL2 -OBJECT_AGG -OBJECT_CONSTRUCT -OBJECT_CONSTRUCT_KEEP_NULL -OBJECT_CONSTRUCT_KEEP_NULL_STRUCTURED -OBJECT_DELETE -OBJECT_INSERT -OBJECT_KEYS -OBJECT_PICK -OCTET_LENGTH -ONLINE_FEATURE_TABLE_REFRESH_HISTORY -OUTLIER_COUNT -OUTLIER_IQR_COUNT -OUTLIER_IQR_PERCENT -OUTLIER_PERCENT -OUTLIER_ZSCORE_COUNT -OUTLIER_ZSCORE_PERCENT -PARSE_DOCUMENT -PARSE_IP -PARSE_JSON -PARSE_URL -PARSE_XML -PERCENTILE_CONT -PERCENTILE_DISC -PERCENT_RANK -PI -PIPE_USAGE_HISTORY -POLICY_CONTEXT -POLICY_REFERENCES -POSITION -POW -POWER -PREVIOUS_DAY -PROMPT -QUARTER -QUERY_ACCELERATION_HISTORY -QUERY_HISTORY -RADIANS -RANDOM -RANDSTR -RANK -RATIO_TO_REPORT -REDUCE -REGEXP -REGEXP_COUNT -REGEXP_INSTR -REGEXP_LIKE -REGEXP_REPLACE -REGEXP_SUBSTR -REGEXP_SUBSTR_ALL -REGR_AVGX -REGR_AVGY -REGR_COUNT -REGR_INTERCEPT -REGR_R2 -REGR_SLOPE -REGR_SXX -REGR_SXY -REGR_SYY -REGR_VALX -REGR_VALY -REPEAT -REPLACE -REPLICATION_GROUP_DANGLING_REFERENCES -REPLICATION_GROUP_REFRESH_HISTORY -REPLICATION_GROUP_REFRESH_HISTORY_ALL -REPLICATION_GROUP_REFRESH_PROGRESS -REPLICATION_GROUP_REFRESH_PROGRESS_ALL -REPLICATION_GROUP_REFRESH_PROGRESS_BY_JOB -REPLICATION_GROUP_USAGE_HISTORY -REPLICATION_USAGE_HISTORY -REST_EVENT_HISTORY -RESULT_SCAN -REVERSE -RIGHT -RLIKE -ROUND -ROW_COUNT -ROW_NUMBER -RPAD -RTRIM -RTRIMMED_LENGTH -SANITIZE_WEBHOOK_CONTENT -SCHEDULED_TIME -SEARCH -SEARCH_IP -SEARCH_OPTIMIZATION_HISTORY -SEARCH_PREVIEW -SECOND -SENTIMENT -SEQ1 -SEQ2 -SEQ4 -SEQ8 -SERVERLESS_ALERT_HISTORY -SERVERLESS_TASK_HISTORY -SET_SYS_CONTEXT -SHA1 -SHA1_BINARY -SHA1_HEX -SHA2 -SHA2_BINARY -SHA2_HEX -SHOW_PYTHON_PACKAGES_DEPENDENCIES -SIGN -SIN -SINH -SKEW -SOUNDEX -SOUNDEX_P123 -SPACE -SPLIT -SPLIT_PART -SPLIT_TEXT_MARKDOWN_HEADER -SPLIT_TEXT_RECURSIVE_CHARACTER -SPLIT_TO_TABLE -SQRT -SQUARE -STAGE_DIRECTORY_FILE_REGISTRATION_HISTORY -STAGE_STORAGE_USAGE_HISTORY -STARTSWITH -STDDEV -STDDEV_POP -STDDEV_SAMP -STORAGE_LIFECYCLE_POLICY_HISTORY -STRIP_NULL_VALUE -STRTOK -STRTOK_SPLIT_TO_TABLE -STRTOK_TO_ARRAY -ST_AREA -ST_ASBINARY -ST_ASEWKB -ST_ASEWKT -ST_ASGEOJSON -ST_ASTEXT -ST_ASWKB -ST_ASWKT -ST_AZIMUTH -ST_BUFFER -ST_CENTROID -ST_COLLECT -ST_CONTAINS -ST_COVEREDBY -ST_COVERS -ST_DIFFERENCE -ST_DIMENSION -ST_DISJOINT -ST_DISTANCE -ST_DWITHIN -ST_ENDPOINT -ST_ENVELOPE -ST_GEOGFROMGEOHASH -ST_GEOGPOINTFROMGEOHASH -ST_GEOGRAPHYFROMWKB -ST_GEOGRAPHYFROMWKT -ST_GEOHASH -ST_GEOMETRYFROMWKB -ST_GEOMETRYFROMWKT -ST_GEOMFROMGEOHASH -ST_GEOMPOINT -ST_GEOMPOINTFROMGEOHASH -ST_HAUSDORFFDISTANCE -ST_INTERPOLATE -ST_INTERSECTION -ST_INTERSECTION_AGG -ST_INTERSECTS -ST_ISVALID -ST_LENGTH -ST_MAKEGEOMPOINT -ST_MAKELINE -ST_MAKEPOINT -ST_MAKEPOLYGON -ST_MAKEPOLYGONORIENTED -ST_NPOINTS -ST_NUMPOINTS -ST_PERIMETER -ST_POINT -ST_POINTN -ST_POLYGON -ST_SETSRID -ST_SIMPLIFY -ST_SRID -ST_STARTPOINT -ST_SYMDIFFERENCE -ST_TRANSFORM -ST_UNION -ST_UNION_AGG -ST_WITHIN -ST_X -ST_XMAX -ST_XMIN -ST_Y -ST_YMAX -ST_YMIN -SUBSTR -SUBSTRING -SUM -SYSDATE -SYSTEM$ABORT_SESSION -SYSTEM$ABORT_TRANSACTION -SYSTEM$ACTIVATE_CMK_INFO -SYSTEM$ACTIVATE_CMK_INFO_POSTGRES -SYSTEM$ADD_EVENT -SYSTEM$ADD_REFERENCE -SYSTEM$ALLOWLIST -SYSTEM$ALLOWLIST_PRIVATELINK -SYSTEM$APPLICATION_GET_LOG_LEVEL -SYSTEM$APPLICATION_GET_METRIC_LEVEL -SYSTEM$APPLICATION_GET_TRACE_LEVEL -SYSTEM$APP_COMPATIBILITY_CHECK -SYSTEM$AUTHORIZE_PRIVATELINK -SYSTEM$AUTHORIZE_STAGE_PRIVATELINK_ACCESS -SYSTEM$AUTO_REFRESH_STATUS -SYSTEM$BEGIN_DEBUG_APPLICATION -SYSTEM$BEHAVIOR_CHANGE_BUNDLE_STATUS -SYSTEM$BLOCK_INTERNAL_STAGES_PUBLIC_ACCESS -SYSTEM$BLOCK_INTERNAL_STAGES_PUBLIC_ACCESS_WITH_EXCEPTION -SYSTEM$CANCEL_ALL_QUERIES -SYSTEM$CANCEL_QUERY -SYSTEM$CATALOG_LINK_STATUS -SYSTEM$CKE_HASH_FUNCTION -SYSTEM$CLEANUP_DATABASE_ROLE_GRANTS -SYSTEM$CLIENT_VERSION_INFO -SYSTEM$CLIENT_VULNERABILITY_INFO -SYSTEM$CLUSTERING_DEPTH -SYSTEM$CLUSTERING_INFORMATION -SYSTEM$COMMIT_MOVE_ORGANIZATION_ACCOUNT -SYSTEM$CONVERT_PIPES_SQS_TO_SNS -SYSTEM$CREATE_BILLING_EVENT -SYSTEM$CREATE_BILLING_EVENTS -SYSTEM$CURRENT_USER_TASK_NAME -SYSTEM$DATABASE_REFRESH_PROGRESS -SYSTEM$DATA_METRIC_SCAN -SYSTEM$DEACTIVATE_CMK_INFO -SYSTEM$DECODE_PAT -SYSTEM$DEPROVISION_PRIVATELINK_ENDPOINT -SYSTEM$DEPROVISION_PRIVATELINK_ENDPOINT_TSS -SYSTEM$DEREGISTER_CMK_INFO -SYSTEM$DEREGISTER_CMK_INFO_POSTGRES -SYSTEM$DESC_ICEBERG_ACCESS_IDENTITY -SYSTEM$DISABLE_BEHAVIOR_CHANGE_BUNDLE -SYSTEM$DISABLE_DATABASE_REPLICATION -SYSTEM$DISABLE_GLOBAL_DATA_SHARING_FOR_ACCOUNT -SYSTEM$DISABLE_PREVIEW_ACCESS -SYSTEM$DISABLE_PRIVATELINK_ACCESS_ONLY -SYSTEM$ENABLE_BEHAVIOR_CHANGE_BUNDLE -SYSTEM$ENABLE_GLOBAL_DATA_SHARING_FOR_ACCOUNT -SYSTEM$ENABLE_PREVIEW_ACCESS -SYSTEM$ENCODE_CKE_PRIMARY_KEY -SYSTEM$END_DEBUG_APPLICATION -SYSTEM$ENFORCE_PRIVATELINK_ACCESS_ONLY -SYSTEM$ESTIMATE_AUTOMATIC_CLUSTERING_COSTS -SYSTEM$ESTIMATE_QUERY_ACCELERATION -SYSTEM$ESTIMATE_SEARCH_OPTIMIZATION_COSTS -SYSTEM$EVALUATE_DATA_QUALITY_EXPECTATIONS -SYSTEM$EVALUATE_DATA_QUALITY_EXPECTATIONS_PERSIST_RESULT -SYSTEM$EXPLAIN_JSON_TO_TEXT -SYSTEM$EXPLAIN_PLAN_JSON -SYSTEM$EXPORT_TDS_FROM_SEMANTIC_VIEW -SYSTEM$EXTERNAL_TABLE_PIPE_STATUS -SYSTEM$FINISH_OAUTH_FLOW -SYSTEM$GENERATE_SAML_CSR -SYSTEM$GENERATE_SCIM_ACCESS_TOKEN -SYSTEM$GET_ALL_DEFAULT_COLUMNS_OVERRIDES -SYSTEM$GET_ALL_REFERENCES -SYSTEM$GET_AWS_SNS_IAM_POLICY -SYSTEM$GET_CATALOG_LINKED_DATABASE_CONFIG -SYSTEM$GET_CLASSIFICATION_RESULT -SYSTEM$GET_CMK_AKV_CONSENT_URL -SYSTEM$GET_CMK_CONFIG -SYSTEM$GET_CMK_CONFIG_POSTGRES -SYSTEM$GET_CMK_INFO -SYSTEM$GET_CMK_INFO_POSTGRES -SYSTEM$GET_CMK_KMS_KEY_POLICY -SYSTEM$GET_COMPUTE_POOL_PENDING_MAINTENANCE -SYSTEM$GET_DBT_LOG -SYSTEM$GET_DEBUG_STATUS -SYSTEM$GET_DEFAULT_COLUMNS_OVERRIDE_FOR_SHOW_COMMAND -SYSTEM$GET_DEFAULT_COLUMNS_OVERRIDE_FOR_SYSTEM_OBJECT -SYSTEM$GET_DIRECTORY_TABLE_STATUS -SYSTEM$GET_GCP_KMS_CMK_GRANT_ACCESS_CMD -SYSTEM$GET_HASH_FOR_APPLICATION -SYSTEM$GET_ICEBERG_TABLE_INFORMATION -SYSTEM$GET_INSTANCE_FAMILY_PLACEMENT_GROUPS -SYSTEM$GET_LOGIN_FAILURE_DETAILS -SYSTEM$GET_PREDECESSOR_RETURN_VALUE -SYSTEM$GET_PREVIEW_ACCESS_STATUS -SYSTEM$GET_PRIVATELINK -SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS -SYSTEM$GET_PRIVATELINK_CONFIG -SYSTEM$GET_PRIVATELINK_ENDPOINTS_INFO -SYSTEM$GET_PRIVATELINK_ENDPOINT_REGISTRATIONS -SYSTEM$GET_PURCHASE_ATTRIBUTES -SYSTEM$GET_REFERENCED_OBJECT_ID_HASH -SYSTEM$GET_RESULTSET_STATUS -SYSTEM$GET_SERVICE_DNS_DOMAIN -SYSTEM$GET_SERVICE_LOGS -SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES -SYSTEM$GET_SNOWFLAKE_PLATFORM_INFO -SYSTEM$GET_STAGE_PRIVATELINK_AUTHORIZED_ENDPOINTS -SYSTEM$GET_TAG -SYSTEM$GET_TAG_ALLOWED_VALUES -SYSTEM$GET_TAG_ON_CURRENT_COLUMN -SYSTEM$GET_TAG_ON_CURRENT_TABLE -SYSTEM$GET_TASK_GRAPH_CONFIG -SYSTEM$GLOBAL_ACCOUNT_SET_PARAMETER -SYSTEM$HOLD_PRIVILEGE_ON_ACCOUNT -SYSTEM$INITIATE_MOVE_ORGANIZATION_ACCOUNT -SYSTEM$INTERNAL_STAGES_PUBLIC_ACCESS_STATUS -SYSTEM$IS_APPLICATION_ALL_MANDATORY_TELEMETRY_EVENT_DEFINITIONS_ENABLED -SYSTEM$IS_APPLICATION_AUTHORIZED_FOR_TELEMETRY_EVENT_SHARING -SYSTEM$IS_APPLICATION_INSTALLED_FROM_SAME_ACCOUNT -SYSTEM$IS_APPLICATION_SHARING_EVENTS_WITH_PROVIDER -SYSTEM$IS_GLOBAL_DATA_SHARING_ENABLED_FOR_ACCOUNT -SYSTEM$IS_LISTING_PURCHASED -SYSTEM$IS_LISTING_TRIAL -SYSTEM$LAST_CHANGE_COMMIT_TIME -SYSTEM$LINK_ACCOUNT_OBJECTS_BY_NAME -SYSTEM$LINK_ORGANIZATION_USER -SYSTEM$LINK_ORGANIZATION_USER_GROUP -SYSTEM$LIST_APPLICATION_RESTRICTED_FEATURES -SYSTEM$LIST_ICEBERG_TABLES_FROM_CATALOG -SYSTEM$LIST_NAMESPACES_FROM_CATALOG -SYSTEM$LOCATE_DBT_ARCHIVE -SYSTEM$LOCATE_DBT_ARTIFACTS -SYSTEM$LOG -SYSTEM$MIGRATE_SAML_IDP_REGISTRATION -SYSTEM$OPT_IN_INTERNAL_STAGE_NETWORK_LOGS -SYSTEM$OPT_OUT_INTERNAL_STAGE_NETWORK_LOGS -SYSTEM$OPT_OUT_MALICIOUS_IP_PROTECTION_BY_CATEGORY -SYSTEM$PIPE_FORCE_RESUME -SYSTEM$PIPE_REBINDING_WITH_NOTIFICATION_CHANNEL -SYSTEM$PIPE_STATUS -SYSTEM$PROVISION_PRIVATELINK_ENDPOINT -SYSTEM$PROVISION_PRIVATELINK_ENDPOINT_TSS -SYSTEM$QUERY_REFERENCE -SYSTEM$READ_OSI_YAML_FROM_SEMANTIC_VIEW -SYSTEM$READ_YAML_FROM_SEMANTIC_VIEW -SYSTEM$REFERENCE -SYSTEM$REGISTER_CMK_INFO -SYSTEM$REGISTER_CMK_INFO_POSTGRES -SYSTEM$REGISTER_PRIVATELINK_ENDPOINT -SYSTEM$REMOVE_ALL_REFERENCES -SYSTEM$REMOVE_REFERENCE -SYSTEM$REPORT_HEALTH_STATUS -SYSTEM$RESOLVE_PYTHON_PACKAGES -SYSTEM$RESTORE_PRIVATELINK_ENDPOINT -SYSTEM$RESTORE_PRIVATELINK_ENDPOINT_TSS -SYSTEM$REVOKE_PRIVATELINK -SYSTEM$REVOKE_STAGE_PRIVATELINK_ACCESS -SYSTEM$SCHEDULE_ASYNC_REPLICATION_GROUP_REFRESH -SYSTEM$SEND_NOTIFICATIONS_TO_CATALOG -SYSTEM$SET_APPLICATION_RESTRICTED_FEATURE_ACCESS -SYSTEM$SET_CATALOG_INTEGRATION -SYSTEM$SET_DEFAULT_COLUMNS_OVERRIDE_FOR_SHOW_COMMAND -SYSTEM$SET_DEFAULT_COLUMNS_OVERRIDE_FOR_SYSTEM_OBJECT -SYSTEM$SET_EVENT_SHARING_ACCOUNT_FOR_REGION -SYSTEM$SET_PRIVATELINK_ENDPOINT_HOSTNAME -SYSTEM$SET_REFERENCE -SYSTEM$SET_RETURN_VALUE -SYSTEM$SET_ROW_TIMESTAMP_ON_ALL_SUPPORTED_TABLES -SYSTEM$SET_SPAN_ATTRIBUTES -SYSTEM$SHOW_ACTIVE_BEHAVIOR_CHANGE_BUNDLES -SYSTEM$SHOW_BUDGETS_FOR_RESOURCE -SYSTEM$SHOW_BUDGETS_IN_ACCOUNT -SYSTEM$SHOW_EVENT_SHARING_ACCOUNTS -SYSTEM$SHOW_MOVE_ORGANIZATION_ACCOUNT_STATUS -SYSTEM$SHOW_OAUTH_CLIENT_SECRETS -SYSTEM$SHOW_SENSITIVE_DATA_MONITORED_ENTITIES -SYSTEM$SNOWPIPE_STREAMING_UPDATE_CHANNEL_OFFSET_TOKEN -SYSTEM$START_OAUTH_FLOW -SYSTEM$START_USER_EMAIL_VERIFICATION -SYSTEM$STREAM_BACKLOG -SYSTEM$STREAM_GET_TABLE_TIMESTAMP -SYSTEM$STREAM_HAS_DATA -SYSTEM$SUPPORTED_DBT_VERSIONS -SYSTEM$TASK_DEPENDENTS_ENABLE -SYSTEM$TASK_RUNTIME_INFO -SYSTEM$TRIGGER_LISTING_REFRESH -SYSTEM$TYPEOF -SYSTEM$UNBLOCK_INTERNAL_STAGES_PUBLIC_ACCESS -SYSTEM$UNLINK_ORGANIZATION_USER -SYSTEM$UNLINK_ORGANIZATION_USER_GROUP -SYSTEM$UNREGISTER_PRIVATELINK_ENDPOINT -SYSTEM$UNSET_DEFAULT_COLUMNS_OVERRIDE_FOR_SHOW_COMMAND -SYSTEM$UNSET_DEFAULT_COLUMNS_OVERRIDE_FOR_SYSTEM_OBJECT -SYSTEM$UNSET_EVENT_SHARING_ACCOUNT_FOR_REGION -SYSTEM$USER_TASK_CANCEL_ONGOING_EXECUTIONS -SYSTEM$VALIDATE_STORAGE_INTEGRATION -SYSTEM$VERIFY_CATALOG_INTEGRATION -SYSTEM$VERIFY_CMK_INFO -SYSTEM$VERIFY_CMK_INFO_POSTGRES -SYSTEM$VERIFY_EXTERNAL_OAUTH_TOKEN -SYSTEM$VERIFY_EXTERNAL_VOLUME -SYSTEM$WAIT -SYSTEM$WAIT_FOR_SERVICES -SYSTIMESTAMP -SYS_CONNECT_BY_PATH -SYS_CONTEXT -TAG_REFERENCES -TAG_REFERENCES_ALL_COLUMNS -TAG_REFERENCES_WITH_LINEAGE -TAN -TANH -TASK_DEPENDENTS -TASK_HISTORY -TEXT_HTML -TEXT_PLAIN -TIME -TIMEADD -TIMEDIFF -TIMESTAMPADD -TIMESTAMPDIFF -TIMESTAMP_FROM_PARTS -TIME_FROM_PARTS -TIME_SLICE -TO_ARRAY -TO_BINARY -TO_BOOLEAN -TO_CHAR -TO_DATE -TO_DECFLOAT -TO_DECIMAL -TO_DOUBLE -TO_FILE -TO_GEOGRAPHY -TO_GEOMETRY -TO_JSON -TO_NUMBER -TO_NUMERIC -TO_OBJECT -TO_QUERY -TO_TIME -TO_TIMESTAMP -TO_TIMESTAMP_LTZ -TO_TIMESTAMP_NTZ -TO_TIMESTAMP_TZ -TO_UUID -TO_VARCHAR -TO_VARIANT -TO_XML -TRANSFORM -TRANSLATE -TRIM -TRUNC -TRUNCATE -TRY_BASE64_DECODE_BINARY -TRY_BASE64_DECODE_STRING -TRY_CAST -TRY_COMPLETE -TRY_DECRYPT -TRY_DECRYPT_RAW -TRY_HEX_DECODE_BINARY -TRY_HEX_DECODE_STRING -TRY_PARSE_JSON -TRY_TO_BINARY -TRY_TO_BOOLEAN -TRY_TO_DATE -TRY_TO_DECFLOAT -TRY_TO_DECIMAL -TRY_TO_DOUBLE -TRY_TO_FILE -TRY_TO_GEOGRAPHY -TRY_TO_GEOMETRY -TRY_TO_NUMBER -TRY_TO_NUMERIC -TRY_TO_TIME -TRY_TO_TIMESTAMP -TRY_TO_TIMESTAMP_LTZ -TRY_TO_TIMESTAMP_NTZ -TRY_TO_TIMESTAMP_TZ -TRY_TO_UUID -TYPEOF -UNICODE -UNIFORM -UNIQUE_COUNT -UPPER -UUID_STRING -VALIDATE -VALIDATE_PIPE_LOAD -VARIANCE -VARIANCE_POP -VARIANCE_SAMP -VAR_POP -VAR_SAMP -VECTOR_AVG -VECTOR_COSINE_SIMILARITY -VECTOR_INNER_PRODUCT -VECTOR_L1_DISTANCE -VECTOR_L2_DISTANCE -VECTOR_MAX -VECTOR_MIN -VECTOR_NORMALIZE -VECTOR_SUM -VECTOR_TRUNCATE -WAREHOUSE_LOAD_HISTORY -WAREHOUSE_METERING_HISTORY -WEEK -WEEKISO -WEEKOFYEAR -WIDTH_BUCKET -XMLGET -YEAR -YEAROFWEEK -YEAROFWEEKISO -ZEROIFNULL -ZIPF diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs index c3a37312e8..fe304fd8b0 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs @@ -35,12 +35,11 @@ pub(crate) fn sqlite_temporal_cast_warnings( '2026-01-01 10:00:00' becomes 2026", cast.type_name ); - cursor.advance(sql, cast.start); - let start: usize = cursor.characters; - let end: usize = start + sql[cast.start..cast.end].chars().count(); - ValidationError::warning(message, SQLITE_TEMPORAL_CAST_CODE) - .with_location(cursor.line, cursor.column) - .with_span(Some(start), Some(end)) + cursor.locate( + sql, + (cast.start, cast.end), + ValidationError::warning(message, SQLITE_TEMPORAL_CAST_CODE), + ) }) .collect() } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs index 896b12f20d..350eac4856 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs @@ -37,12 +37,11 @@ pub(crate) fn unsupported_truth_test_errors( spelling(&test), rewrite(dialect, &test) ); - cursor.advance(sql, test.start); - let start: usize = cursor.characters; - let end: usize = start + sql[test.start..test.end].chars().count(); - ValidationError::error(message, UNSUPPORTED_TRUTH_TEST_CODE) - .with_location(cursor.line, cursor.column) - .with_span(Some(start), Some(end)) + cursor.locate( + sql, + (test.start, test.end), + ValidationError::error(message, UNSUPPORTED_TRUTH_TEST_CODE), + ) }) .collect() } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs b/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs index 73e179774e..75bfd83774 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs @@ -1,11 +1,9 @@ -use crate::semantic_validation::models::FunctionProbes; use polyglot_sql::{DialectType, ValidationResult}; pub(crate) fn map_diagnostics( sql: &str, dialect: DialectType, result: ValidationResult, - probes: &FunctionProbes, ) -> Result { - crate::semantic_validation::_helpers::diagnostics::map_diagnostics(sql, dialect, result, probes) + crate::semantic_validation::_helpers::diagnostics::map_diagnostics(sql, dialect, result) } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/models.rs b/crates/sqlbuild-analysis/src/semantic_validation/models.rs index a24345c77a..a6f184a32c 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/models.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/models.rs @@ -1,6 +1,6 @@ //! Compile-owned native catalog, mapping state and boundary requests. -use crate::semantic_validation::types::{PreparedCompactAnalysis, ProbeKey, Relations}; +use crate::semantic_validation::types::{PreparedCompactAnalysis, Relations}; use polyglot_sql::validation::SchemaTable; use polyglot_sql::{DialectType, SchemaValidationOptions}; use rayon::ThreadPool; @@ -65,12 +65,6 @@ pub struct PositionInput { pub passes: Vec>, } -/// Memoized call-site function-name probes, owned by the catalog that validates a compile. -#[derive(Debug, Default)] -pub(crate) struct FunctionProbes { - pub(crate) suggestions: Mutex>>, -} - pub struct ProjectCatalog { pub(crate) dialect: DialectType, pub(crate) options: SchemaValidationOptions, @@ -78,7 +72,6 @@ pub struct ProjectCatalog { pub(super) tables: HashMap, pub(super) overrides: Vec>, pub(super) analysis_tables: HashMap, - pub(crate) function_probes: Arc, /// One analysis pool per compile catalog, built on first use and shared by its views. pub(super) analysis_pool: Arc>>>, /// Analysis batches running on the pool; preparation only borrows the pool while none run. diff --git a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs index 11698e22b1..9003dcfa2a 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs @@ -1,6 +1,5 @@ use crate::semantic_validation::_helpers::diagnostics::map_diagnostics; use crate::semantic_validation::_helpers::function_names::unsupported_calls; -use crate::semantic_validation::models::FunctionProbes; use crate::semantic_validation::tests::test_types::{ LocatedFunctionDiagnosticTestCase, UnsupportedFunctionTestCase, }; @@ -11,25 +10,25 @@ fn given_unsupported_builtin_spelling_when_scanning_then_reports_dialect_spellin let test_cases = [ UnsupportedFunctionTestCase { description: "Snowflake STARTS_WITH suggests STARTSWITH", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT STARTS_WITH(product_name, 'a') AS flagged FROM products", expected_calls: &[("STARTS_WITH", "STARTSWITH")], }, UnsupportedFunctionTestCase { description: "matching is case-insensitive and keeps the authored spelling", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "select ends_with(product_name, 'a'), lcase(product_name) from products", expected_calls: &[("ends_with", "ENDSWITH"), ("lcase", "LOWER")], }, UnsupportedFunctionTestCase { description: "conditional and length aliases suggest the Snowflake spelling", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT IF(quantity > 0, 1, 0), CHAR_LENGTH(product_name) FROM products", expected_calls: &[("IF", "IFF"), ("CHAR_LENGTH", "LENGTH")], }, UnsupportedFunctionTestCase { description: "calls inside windows, lambdas and table functions are checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT SUM(quantity) OVER (PARTITION BY UCASE(category)), \ FILTER(tags, tag -> STARTS_WITH(tag, 'a')) \ FROM products, TABLE(FLATTEN(input => ARRAY_CONSTRUCT(LCASE(sku))))", @@ -41,26 +40,26 @@ fn given_unsupported_builtin_spelling_when_scanning_then_reports_dialect_spellin }, UnsupportedFunctionTestCase { description: "an object literal value after a string key is checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT {'featured': STARTS_WITH(product_name, 'a')} FROM products", expected_calls: &[("STARTS_WITH", "STARTSWITH")], }, UnsupportedFunctionTestCase { description: "a MATCH_RECOGNIZE DEFINE condition after AS is checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT * FROM products MATCH_RECOGNIZE (ORDER BY sku PATTERN (featured) \ DEFINE featured AS STARTS_WITH(product_name, 'a'))", expected_calls: &[("STARTS_WITH", "STARTSWITH")], }, UnsupportedFunctionTestCase { description: "comments between the name and its parenthesis are skipped", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT LCASE /* lower */ (sku), UCASE -- upper\n (sku) FROM products", expected_calls: &[("LCASE", "LOWER"), ("UCASE", "UPPER")], }, UnsupportedFunctionTestCase { description: "operand FROM and select lists inside a FROM subquery are checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT TRIM(BOTH 'x' FROM LCASE(sku)), sku IS DISTINCT FROM UCASE(sku) \ FROM (SELECT sku, LCASE(sku) AS lowered FROM products) AS listed, catalog \ WHERE sku IN (1, 2) AND LCASE(sku) = 'a'", @@ -73,19 +72,19 @@ fn given_unsupported_builtin_spelling_when_scanning_then_reports_dialect_spellin }, UnsupportedFunctionTestCase { description: "a call followed by AS NOT without MATERIALIZED is still checked", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "SELECT STARTSWITH(product_name, 'a') AS not_featured FROM products", expected_calls: &[("STARTSWITH", "STARTS_WITH")], }, UnsupportedFunctionTestCase { description: "DuckDB STARTSWITH suggests the catalogue spelling", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "SELECT STARTSWITH(product_name, 'a') FROM products", expected_calls: &[("STARTSWITH", "STARTS_WITH")], }, ]; for test_case in &test_cases { - let calls = unsupported_calls(test_case.sql, test_case.dialect, &FunctionProbes::default()); + let calls = unsupported_calls(test_case.sql, test_case.dialect); let observed: Vec<(&str, &str)> = calls .iter() .map(|call| { @@ -109,7 +108,7 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { let test_cases = [ UnsupportedFunctionTestCase { description: "listed Snowflake synonyms are accepted", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT STARTSWITH(a, 'x'), ENDSWITH(a, 'x'), CONTAINS(a, 'x'), SUBSTR(a, 1, 2), \ SUBSTRING(a, 1, 2), TIMESTAMPDIFF(day, b, c), DATEDIFF(day, b, c), \ TRY_TO_DECIMAL(a, 10, 2), TRY_TO_NUMBER(a), IFF(d, 1, 0), LEN(a), \ @@ -119,14 +118,14 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "quoted identifiers, literals and comments are not call sites", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT \"STARTS_WITH\"(a, 'x'), 'STARTS_WITH(a, b)', $$LCASE(a)$$ \ -- STARTS_WITH(a, 'x')\n /* LCASE(a) */ FROM products", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "methods, qualified functions, sentinels and macros are not checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT tools.starts_with(a, 'x'), analytics.helpers.lcase(a), \ __sqlbuild_udf_starts_with(a, 'x'), @py.starts_with(a), payload:lcase(a) \ FROM __ref(\"products\")", @@ -134,13 +133,13 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "unknown and user-defined function names are never flagged", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT normalize_sku(a), DATE_ADD(day, 1, b), my_udf() FROM products", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "type positions and keywords before parentheses are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "WITH ranked (sku, position_rank) AS (SELECT DISTINCT (a), \ ROW_NUMBER() OVER (ORDER BY b) FROM products WHERE NOT (a IN (1, 2)) \ AND EXISTS (SELECT 1)) \ @@ -149,14 +148,14 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "CTE column lists are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "WITH listed AS (SELECT 1 AS dow), day_of_week (dow) AS (SELECT 1), \ lcase(x) AS (SELECT 2) SELECT dow FROM day_of_week", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "materialized CTE column lists are not calls", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "WITH day_of_week (d) AS MATERIALIZED (SELECT 1), \ day_of_month (d) AS /* hint */ NOT -- keep\n MATERIALIZED (SELECT 2) \ SELECT * FROM day_of_week", @@ -164,7 +163,7 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "derived-table, table-function and table aliases are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT x FROM (VALUES (1)) day_of_week (x), products lcase (y), \ TABLE(FLATTEN(input => tags)) ucase (z), day_of_month (w) \ JOIN char_length (v) ON TRUE", @@ -172,27 +171,27 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "INSERT and CREATE column lists are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "CREATE TABLE day_of_week (dow INTEGER); CREATE VIEW lcase (x) AS SELECT 1; \ INSERT INTO ucase (x) SELECT 1; CREATE TABLE IF NOT EXISTS char_length (x INT)", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "dialects without a spelling table are not checked", - dialect: DialectType::PostgreSQL, + dialect: "postgres", sql: "SELECT STARTS_WITH(a, 'x'), LCASE(a) FROM products", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "DuckDB catalogue spellings are accepted", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "SELECT starts_with(a, 'x'), prefix(a, 'x'), lcase(a), substr(a, 1, 2) \ FROM products", expected_calls: &[], }, ]; for test_case in &test_cases { - let calls = unsupported_calls(test_case.sql, test_case.dialect, &FunctionProbes::default()); + let calls = unsupported_calls(test_case.sql, test_case.dialect); let observed: Vec<(&str, &str)> = calls .iter() .map(|call| (call.name.as_str(), call.suggestion.as_str())) @@ -225,7 +224,6 @@ fn given_unsupported_spelling_when_mapping_diagnostics_then_returns_located_b101 valid: true, errors: Vec::new(), }, - &FunctionProbes::default(), )?; let [error] = result.errors.as_slice() else { return Err(format!("{}: {:?}", test_case.description, result.errors)); diff --git a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs index f9ba73f68f..6b9865dd9b 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs @@ -1,6 +1,5 @@ use crate::semantic_validation::_helpers::diagnostics::map_diagnostics; use crate::semantic_validation::_helpers::truth_tests::{rejects_truth_tests, truth_tests}; -use crate::semantic_validation::models::FunctionProbes; use crate::semantic_validation::tests::test_types::{ LocatedTruthTestDiagnosticTestCase, TruthTestDialectTestCase, TruthTestScanTestCase, }; @@ -91,7 +90,6 @@ fn given_dialect_when_checking_truth_tests_then_only_rejecting_warehouses_report valid: true, errors: Vec::new(), }, - &FunctionProbes::default(), ) .expect(test_case.description); assert_eq!( @@ -162,7 +160,6 @@ fn given_rejected_truth_test_when_mapping_diagnostics_then_returns_located_b103( valid: true, errors: Vec::new(), }, - &FunctionProbes::default(), )?; let [error] = result.errors.as_slice() else { return Err(format!("{}: {:?}", test_case.description, result.errors)); diff --git a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs index 201a5dc6fc..7fcc899e20 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs @@ -12,7 +12,7 @@ pub(crate) struct SemanticValidationBatchTestCase { pub(crate) struct UnsupportedFunctionTestCase { pub(crate) description: &'static str, - pub(crate) dialect: polyglot_sql::DialectType, + pub(crate) dialect: &'static str, pub(crate) sql: &'static str, pub(crate) expected_calls: &'static [(&'static str, &'static str)], } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/types.rs b/crates/sqlbuild-analysis/src/semantic_validation/types.rs index 8ab94dd7a8..02480b2f00 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/types.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/types.rs @@ -5,7 +5,6 @@ pub type Relations = HashMap; pub type BindingRequest = (String, Vec<(String, bool)>, Relations); /// One analysis SQL with its relation stubs and placeholder defaults. pub type NormalizationRequest = (String, HashMap, HashMap); -pub(crate) type ProbeKey = (polyglot_sql::DialectType, String, usize); /// Compact analysis whose catalog schemas are resolved; it runs with an options-only catalog. pub(crate) type PreparedCompactAnalysis = Box Result + Send>; diff --git a/crates/sqlbuild-analysis/src/sql_tree/_helpers/mod.rs b/crates/sqlbuild-analysis/src/sql_tree/_helpers/mod.rs index 0598c2c140..942bd41b25 100644 --- a/crates/sqlbuild-analysis/src/sql_tree/_helpers/mod.rs +++ b/crates/sqlbuild-analysis/src/sql_tree/_helpers/mod.rs @@ -1,7 +1,8 @@ -//! Parser selection, parsing, the tree walk, clause nodes and source positions. +//! Parser selection, parsing, the tree walk, clause nodes, source positions and table wrapping. pub(crate) mod clauses; pub(crate) mod parse; pub(crate) mod positions; pub(crate) mod rules_tree_parser; +pub(crate) mod table_wrapped; pub(crate) mod walk; diff --git a/crates/sqlbuild-analysis/src/sql_tree/_helpers/rules_tree_parser.rs b/crates/sqlbuild-analysis/src/sql_tree/_helpers/rules_tree_parser.rs index b330c0a39c..db8eb603d9 100644 --- a/crates/sqlbuild-analysis/src/sql_tree/_helpers/rules_tree_parser.rs +++ b/crates/sqlbuild-analysis/src/sql_tree/_helpers/rules_tree_parser.rs @@ -1,10 +1,7 @@ //! `rules_tree_parser`: the parser that reads a dialect's SQL for rules. -use sqlquack_syntax::Dialect; - -use crate::sql_tree::constants::{ - DUCKDB_DIALECTS, POSTGRES_DIALECTS, SNOWFLAKE_DIALECT, SQLITE_DIALECT, -}; +use crate::sql_tree::constants::POSTGRES_DIALECTS; +use crate::sql_tree::main::shared_dialect::shared_dialect; use crate::sql_tree::models::TreeParser; /// The parser for `dialect`, or `None`; add a dialect (one line) only once its parser passes the depth probe with an empty allowlist. @@ -12,12 +9,5 @@ pub(crate) fn rules_tree_parser(dialect: &str) -> Option { if POSTGRES_DIALECTS.contains(&dialect) { return Some(TreeParser::Postgres); } - if DUCKDB_DIALECTS.contains(&dialect) { - return Some(TreeParser::Shared(Dialect::DuckDB)); - } - match dialect { - SNOWFLAKE_DIALECT => Some(TreeParser::Shared(Dialect::Snowflake)), - SQLITE_DIALECT => Some(TreeParser::Shared(Dialect::SQLite)), - _ => None, - } + shared_dialect(dialect).map(TreeParser::Shared) } diff --git a/crates/sqlbuild-analysis/src/sql_tree/_helpers/table_wrapped.rs b/crates/sqlbuild-analysis/src/sql_tree/_helpers/table_wrapped.rs new file mode 100644 index 0000000000..f55eeb3381 --- /dev/null +++ b/crates/sqlbuild-analysis/src/sql_tree/_helpers/table_wrapped.rs @@ -0,0 +1,66 @@ +//! Snowflake table function markers wrapped in `TABLE( )`, as SQLBuild renders them. + +use std::cmp::Reverse; + +use sqlbuild_sqltext::sql_scan::constants::TABLE_WRAPPER; +use sqlbuild_sqltext::sql_scan::main::dialect_matching_paren::dialect_matching_paren; +use sqlbuild_sqltext::sql_scan::main::follows_table_wrapper::follows_table_wrapper; +use sqlbuild_sqltext::sql_scan::models::LexicalSyntax; + +use crate::relation_markers::constants::{MARKER_NAME_PREFIX, TABLE_FUNCTION_MARKER}; +use crate::relation_markers::types::MarkerCall; + +/// `named` with each table function marker call in `TABLE( )`, written over padding so later text keeps its column. +pub(crate) fn table_wrapped(named: &str, calls: &[MarkerCall], syntax: &LexicalSyntax) -> String { + let bytes = named.as_bytes(); + let mut edits: Vec<(usize, usize, String)> = Vec::new(); + for (index, (function, _)) in calls.iter().enumerate() { + if function != TABLE_FUNCTION_MARKER { + continue; + } + let name = format!("{MARKER_NAME_PREFIX}{index}"); + let Some(start) = name_start(bytes, &name) else { + continue; + }; + let name_end = start + name.len(); + let open = name_end + + bytes[name_end..] + .iter() + .take_while(|byte| byte.is_ascii_whitespace()) + .count(); + if bytes.get(open) != Some(&b'(') || follows_table_wrapper(&bytes[..start]) { + continue; + } + let Ok(close) = dialect_matching_paren(bytes, open, syntax) else { + continue; + }; + let spaces = bytes[name_end..] + .iter() + .take_while(|byte| **byte == b' ') + .count(); + let after = close + 1; + let replaced = usize::from(matches!(bytes.get(after), Some(b' ' | b'\t'))); + let borrowed = spaces.min(TABLE_WRAPPER.len() + 1 - replaced); + edits.push((start, name_end + borrowed, format!("{TABLE_WRAPPER}{name}"))); + edits.push((after, after + replaced, ")".to_owned())); + } + edits.sort_by_key(|(start, _, _)| Reverse(*start)); + let mut wrapped = named.to_owned(); + for (start, end, replacement) in edits { + wrapped.replace_range(start..end, &replacement); + } + wrapped +} + +/// Where `name` first stands as a whole word in `bytes`. +fn name_start(bytes: &[u8], name: &str) -> Option { + let name = name.as_bytes(); + let word = |byte: Option<&u8>| { + byte.is_some_and(|value| value.is_ascii_alphanumeric() || *value == b'_') + }; + (0..bytes.len().saturating_sub(name.len() - 1)).find(|start| { + bytes[*start..].starts_with(name) + && !word(start.checked_sub(1).and_then(|index| bytes.get(index))) + && !word(bytes.get(start + name.len())) + }) +} diff --git a/crates/sqlbuild-analysis/src/sql_tree/main/deeper_than.rs b/crates/sqlbuild-analysis/src/sql_tree/main/deeper_than.rs new file mode 100644 index 0000000000..30d6a9998d --- /dev/null +++ b/crates/sqlbuild-analysis/src/sql_tree/main/deeper_than.rs @@ -0,0 +1,15 @@ +//! `deeper_than`: whether a parsed statement nests past a depth limit. + +use sqlquack_syntax::{Expression, ExpressionWalk}; + +/// Whether `statement` nests more than `limit` levels, walked iteratively so depth cannot overflow. +pub fn deeper_than(statement: &Expression, limit: usize) -> bool { + let mut stack: Vec<(&Expression, usize)> = vec![(statement, 1)]; + while let Some((node, depth)) = stack.pop() { + if depth > limit { + return true; + } + stack.extend(node.children().into_iter().map(|child| (child, depth + 1))); + } + false +} diff --git a/crates/sqlbuild-analysis/src/sql_tree/main/mod.rs b/crates/sqlbuild-analysis/src/sql_tree/main/mod.rs index 0c71a2f7d5..26be427137 100644 --- a/crates/sqlbuild-analysis/src/sql_tree/main/mod.rs +++ b/crates/sqlbuild-analysis/src/sql_tree/main/mod.rs @@ -1,3 +1,5 @@ //! Entry points. +pub mod deeper_than; +pub mod shared_dialect; pub mod sql_tree_nodes; diff --git a/crates/sqlbuild-analysis/src/sql_tree/main/shared_dialect.rs b/crates/sqlbuild-analysis/src/sql_tree/main/shared_dialect.rs new file mode 100644 index 0000000000..5a7e95518e --- /dev/null +++ b/crates/sqlbuild-analysis/src/sql_tree/main/shared_dialect.rs @@ -0,0 +1,22 @@ +//! `shared_dialect`: the grammar of SQLQuack's shared parser that reads a dialect's SQL. + +use sqlquack_syntax::Dialect; + +use crate::sql_tree::constants::{ + DUCKDB_DIALECTS, POSTGRES_DIALECTS, SNOWFLAKE_DIALECT, SQLITE_DIALECT, +}; + +/// The shared-parser grammar for `dialect`, or `None` when the shared parser reads none of it. +pub fn shared_dialect(dialect: &str) -> Option { + if DUCKDB_DIALECTS.contains(&dialect) { + return Some(Dialect::DuckDB); + } + if POSTGRES_DIALECTS.contains(&dialect) { + return Some(Dialect::PostgreSQL); + } + match dialect { + SNOWFLAKE_DIALECT => Some(Dialect::Snowflake), + SQLITE_DIALECT => Some(Dialect::SQLite), + _ => None, + } +} diff --git a/crates/sqlbuild-analysis/src/sql_tree/main/sql_tree_nodes.rs b/crates/sqlbuild-analysis/src/sql_tree/main/sql_tree_nodes.rs index de5e7a8b94..c1a036e032 100644 --- a/crates/sqlbuild-analysis/src/sql_tree/main/sql_tree_nodes.rs +++ b/crates/sqlbuild-analysis/src/sql_tree/main/sql_tree_nodes.rs @@ -1,17 +1,20 @@ //! `sql_tree_nodes`: a rule's SQL syntax tree as nodes in pre-order. +use sqlbuild_sqltext::sql_scan::main::dialect_lexical_syntax::dialect_lexical_syntax; use sqlquack_syntax::Expression; +use crate::relation_markers::main::markers_as_names::markers_as_names; use crate::sql_tree::_helpers::parse::parse; use crate::sql_tree::_helpers::positions::line_starts; use crate::sql_tree::_helpers::rules_tree_parser::rules_tree_parser; +use crate::sql_tree::_helpers::table_wrapped::table_wrapped; use crate::sql_tree::_helpers::walk::walk; -use crate::sql_tree::constants::TREE_WORKER_STACK_BYTES; +use crate::sql_tree::constants::{SNOWFLAKE_DIALECT, TREE_WORKER_STACK_BYTES}; use crate::sql_tree::errors::SqlTreeError; use crate::sql_tree::models::SqlTreeNode; use crate::worker_stack::main::run_on_worker_stack::run_on_worker_stack; -/// Every node of `sql` parsed in `dialect`, in pre-order, joins and CTEs owning their expressions. +/// Every node of `sql` parsed in `dialect`, in pre-order, joins and CTEs owning their expressions; relation markers read as tables. pub fn sql_tree_nodes(sql: &str, dialect: &str) -> Result, SqlTreeError> { run_on_worker_stack(TREE_WORKER_STACK_BYTES, "the parser panicked", || { tree_nodes(sql, dialect) @@ -22,6 +25,13 @@ pub fn sql_tree_nodes(sql: &str, dialect: &str) -> Result, SqlT fn tree_nodes(sql: &str, dialect: &str) -> Result, SqlTreeError> { let parser = rules_tree_parser(dialect) .ok_or_else(|| SqlTreeError::UnavailableDialect(dialect.to_owned()))?; - let statements: Vec = parse(sql, parser)?; - walk(&statements, &line_starts(sql)) + let syntax = dialect_lexical_syntax(dialect); + let (named, calls) = markers_as_names(sql, &syntax).map_err(SqlTreeError::Unanalysable)?; + let named = if dialect == SNOWFLAKE_DIALECT { + table_wrapped(&named, &calls, &syntax) + } else { + named + }; + let statements: Vec = parse(&named, parser)?; + walk(&statements, &line_starts(&named)) } diff --git a/crates/sqlbuild-analysis/src/sql_tree/tests/test_sql_tree.rs b/crates/sqlbuild-analysis/src/sql_tree/tests/test_sql_tree.rs index e1715b2eee..21fda1db44 100644 --- a/crates/sqlbuild-analysis/src/sql_tree/tests/test_sql_tree.rs +++ b/crates/sqlbuild-analysis/src/sql_tree/tests/test_sql_tree.rs @@ -6,6 +6,8 @@ use crate::sql_tree::tests::test_types::{TreeFailureTestCase, TreeShapeTestCase} const CTE_AND_JOIN_SQL: &str = "WITH x AS (SELECT 1 AS id)\nSELECT *\n FROM x JOIN (SELECT 2 AS id) s ON x.id = s.id"; +const SNOWFLAKE_MARKER_SQL: &str = "WITH o AS (SELECT * FROM __ref(\"orders\"))\nSELECT o.*, c.name\nFROM o JOIN __ref(\"customers\") c ON o.id = c.id JOIN (SELECT 2 AS id) s ON s.id = o.id\nWHERE o.id IN (SELECT id FROM __source(\"raw_returns\"))"; + #[test] fn given_sql_when_building_a_rule_tree_then_clauses_own_their_queries_at_their_source_positions() { let test_cases = [ @@ -48,6 +50,60 @@ fn given_sql_when_building_a_rule_tree_then_clauses_own_their_queries_at_their_s ("subquery", 2, 3, Some("alias")), ], }, + TreeShapeTestCase { + description: "Snowflake reads relation markers as tables; nodes after them keep their columns", + dialect: "snowflake", + sql: SNOWFLAKE_MARKER_SQL, + expected_nodes: vec![ + ("select", 1, 1, None), + ("cte", 1, 6, Some("select")), + ("select", 1, 12, Some("cte")), + ("star", 1, 12, Some("select")), + ("star", 2, 8, Some("select")), + ("join", 3, 8, Some("select")), + ("join", 3, 49, Some("select")), + ("select", 3, 54, Some("subquery")), + ("subquery", 3, 54, Some("join")), + ("select", 4, 7, Some("in")), + ], + }, + TreeShapeTestCase { + description: "a Snowflake table function marker reads as SQLBuild renders it, inside TABLE( ), at its columns", + dialect: "snowflake", + sql: "SELECT l.*\nFROM __table_fn(\"lines_for\")('B-1', __ref(\"orders\").k) AS l JOIN (SELECT 1 AS k) s ON s.k = l.k", + expected_nodes: vec![ + ("select", 1, 1, None), + ("star", 1, 8, Some("select")), + ("join", 2, 61, Some("select")), + ("select", 2, 66, Some("subquery")), + ("subquery", 2, 66, Some("join")), + ], + }, + TreeShapeTestCase { + description: "a Snowflake table function marker closing a subquery keeps the columns after it", + dialect: "snowflake", + sql: "SELECT 1\nFROM (SELECT * FROM __table_fn(\"f\")(1)) t JOIN (SELECT 2 AS k) s ON s.k = t.k", + expected_nodes: vec![ + ("select", 1, 1, None), + ("select", 2, 6, Some("subquery")), + ("star", 2, 6, Some("select")), + ("subquery", 2, 6, Some("select")), + ("join", 2, 43, Some("select")), + ("select", 2, 48, Some("subquery")), + ("subquery", 2, 48, Some("join")), + ], + }, + TreeShapeTestCase { + description: "a Snowflake table function marker already inside TABLE( ) is not wrapped twice", + dialect: "snowflake", + sql: "SELECT 1\nFROM TABLE(__table_fn(\"f\")(1)) t JOIN (SELECT 2 AS k) s ON s.k = t.k", + expected_nodes: vec![ + ("select", 1, 1, None), + ("join", 2, 34, Some("select")), + ("select", 2, 39, Some("subquery")), + ("subquery", 2, 39, Some("join")), + ], + }, TreeShapeTestCase { description: "a comma-separated FROM item after an explicit join is not a join", dialect: "duckdb", diff --git a/crates/sqlbuild-analysis/src/type_system/_helpers/mod.rs b/crates/sqlbuild-analysis/src/type_system/_helpers/mod.rs index 70cbe23166..542e409872 100644 --- a/crates/sqlbuild-analysis/src/type_system/_helpers/mod.rs +++ b/crates/sqlbuild-analysis/src/type_system/_helpers/mod.rs @@ -2,4 +2,5 @@ pub(crate) mod dialects; pub(crate) mod fallback; pub(crate) mod parsed; pub(crate) mod python_text; -pub(crate) mod shallow_args; +pub(crate) mod spelling; +pub(crate) mod type_reader; diff --git a/crates/sqlbuild-analysis/src/type_system/_helpers/parsed.rs b/crates/sqlbuild-analysis/src/type_system/_helpers/parsed.rs index 1ee482d2c8..3d2cada562 100644 --- a/crates/sqlbuild-analysis/src/type_system/_helpers/parsed.rs +++ b/crates/sqlbuild-analysis/src/type_system/_helpers/parsed.rs @@ -1,97 +1,121 @@ -//! Python's `_normalize_with_polyglot`: the parsed type's SQL and arguments. +//! A parsed type's normalization: its tag, parameters and dialect spelling. -use polyglot_sql::{ - ComplexityGuardOptions, DataType, Dialect, DialectType, Expression, ParseOptions, -}; -use serde_json::{Map, Value}; +use serde_json::Value; +use sqlquack_syntax::tokens::models::Dialect; use crate::type_system::_helpers::fallback::{ normalize_with_fallback, python_or_default, simple, snowflake_integer, timestamp_normalized_name, }; use crate::type_system::_helpers::python_text::{python_int, split_type_and_params, upper}; -use crate::type_system::_helpers::shallow_args::shallow_args; +use crate::type_system::_helpers::spelling::{SpellingDialect, spell}; +use crate::type_system::_helpers::type_reader::read_type; use crate::type_system::constants::{ BIGNUMERIC_TYPE_NAME, BIGQUERY_STRING_TYPE_NAME, BOOLEAN_TYPE_NAMES, - CUSTOM_NORMALIZATION_TYPE_NAMES, DATE_TYPE_NAME, DATETIME_TYPE_NAME, DECIMAL_TYPE_NAMES, - FLOAT_TYPE_NAMES, FLOAT_WIRE_TYPE_NAME, INTEGER_PARSE_TYPE_NAMES, INTEGER_PRECISION, - INTEGER_SCALE, INTEGER_TYPE_NAMES, MAX_FUNCTION_CALL_DEPTH, POLYGLOT_CUSTOM_TYPE_NAME, - POLYGLOT_TYPE_NAME_ALIASES, SIZED_TEXT_TYPE_NAMES, STRING_TYPE_NAMES, TIMESTAMP_TYPE_NAMES, - UNBOUNDED_TEXT_TYPE_NAMES, VARCHAR_TYPE_NAME, + CUSTOM_NORMALIZATION_TYPE_NAMES, CUSTOM_TYPE_TAG, DATE_TYPE_NAME, DATETIME_TYPE_NAME, + DECIMAL_TYPE_NAMES, FLOAT_TYPE_NAMES, FLOAT_WIRE_TYPE_NAME, INTEGER_PARSE_TYPE_NAMES, + INTEGER_PRECISION, INTEGER_SCALE, INTEGER_TYPE_NAMES, MAX_TYPE_NESTING, SIZED_TEXT_TYPE_NAMES, + STRING_TYPE_NAMES, TIMESTAMP_TYPE_NAMES, TYPE_TAG_ALIASES, UNBOUNDED_TEXT_TYPE_NAMES, + VARCHAR_TYPE_NAME, }; -use crate::type_system::errors::TypeNormalizationError; use crate::type_system::models::{NormalizedType, PythonInteger, TypeDialect, TypeFamily}; +use crate::warehouse_types::main::type_tree::type_tree; +use crate::warehouse_types::models::TypeParser; -/// Parse one type with SQLBuild's Polyglot guard; the error text is what Python logs. -pub(crate) fn parse_type(type_sql: &str, dialect: &Dialect) -> Result { - dialect - .parse_data_type_with_options( - type_sql, - &ParseOptions { - complexity_guard: Some(ComplexityGuardOptions { - max_function_call_depth: Some(MAX_FUNCTION_CALL_DEPTH), - ..Default::default() - }), - }, - ) - .map_err(|error| error.to_string()) +/// The type tree of `type_sql` from SQLQuack's parser, else SQLBuild's reader, or the error. +pub(crate) fn parse_type(type_sql: &str, dialect: SpellingDialect) -> Result { + if nesting_bound(type_sql) > MAX_TYPE_NESTING { + return Err("the type nests too deeply".to_owned()); + } + let parser: TypeParser = match dialect { + SpellingDialect::Snowflake => TypeParser::Core(Dialect::Snowflake), + SpellingDialect::DuckDb => TypeParser::Core(Dialect::DuckDB), + SpellingDialect::Sqlite => TypeParser::Core(Dialect::SQLite), + SpellingDialect::Postgres => TypeParser::Postgres, + SpellingDialect::BigQuery => TypeParser::BigQuery, + SpellingDialect::Generic + | SpellingDialect::Databricks + | SpellingDialect::Tsql + | SpellingDialect::ClickHouse => return read_type(type_sql, dialect), + }; + let native: Result = type_tree(type_sql, parser); + match native { + Ok(tree) if !has_custom(&tree) => Ok(tree), + Ok(tree) => Ok(read_type(type_sql, dialect).unwrap_or(tree)), + Err(error) => read_type(type_sql, dialect).map_err(|_| error), + } } -/// The parsed type's SQL, in generic spelling when the dialect refuses a type it cannot hold. -fn generated_type( - expression: &Expression, - polyglot: &Dialect, -) -> Result { - match polyglot.generate(expression) { - Ok(generated) => Ok(generated), - Err(polyglot_sql::error::Error::Unsupported { .. }) => Dialect::get(DialectType::Generic) - .generate(expression) - .map_err(|error| TypeNormalizationError::Generation(error.to_string())), - Err(error) => Err(TypeNormalizationError::Generation(error.to_string())), +/// A bound on the type tree's depth: the deepest brackets plus the array suffixes. +pub(crate) fn nesting_bound(type_sql: &str) -> usize { + let (mut depth, mut deepest, mut suffixes): (usize, usize, usize) = (0, 0, 0); + for character in type_sql.chars() { + match character { + '(' | '<' => { + depth += 1; + deepest = deepest.max(depth); + } + ')' | '>' => depth = depth.saturating_sub(1), + '[' => suffixes += 1, + _ => {} + } + } + deepest + suffixes +} + +/// Whether `tree` or any type nested in it is an unknown type name. +fn has_custom(tree: &Value) -> bool { + match tree { + Value::Object(fields) => { + fields + .get("data_type") + .and_then(Value::as_str) + .is_some_and(|tag| tag.eq_ignore_ascii_case(CUSTOM_TYPE_TAG)) + || fields.values().any(has_custom) + } + Value::Array(items) => items.iter().any(has_custom), + _ => false, } } -/// Python's `_normalized_from_parsed_type`. +/// The normalization of a parsed type. pub(crate) fn normalize_parsed( - parsed: DataType, - polyglot: &Dialect, + tree: &Value, + spelling: SpellingDialect, dialect: Option, -) -> Result { - let args: Map = shallow_args(&parsed); - let expression: Expression = Expression::DataType(parsed); - let generated: String = generated_type(&expression, polyglot)?; - let normalized_name: String = upper(&generated).replace(' ', ""); - let dtype_name: String = polyglot_type_name(&args); - let params: Vec = polyglot_type_params(&args); +) -> NormalizedType { + let normalized_name: String = upper(&spell(tree, spelling)).replace(' ', ""); + let dtype_name: String = type_name(tree); + let params: Vec = type_params(tree); let snowflake: bool = dialect == Some(TypeDialect::Snowflake); let bigquery: bool = dialect == Some(TypeDialect::BigQuery); let dtype: &str = dtype_name.as_str(); if bigquery && INTEGER_PARSE_TYPE_NAMES.contains(&dtype) { - return Ok(simple("INT64", TypeFamily::Integer)); + return simple("INT64", TypeFamily::Integer); } if bigquery && dtype == BIGNUMERIC_TYPE_NAME { - return Ok(simple("BIGNUMERIC", TypeFamily::Decimal)); + return simple("BIGNUMERIC", TypeFamily::Decimal); } if bigquery && dtype == FLOAT_WIRE_TYPE_NAME { - return Ok(simple("FLOAT64", TypeFamily::Float)); + return simple("FLOAT64", TypeFamily::Float); } - if dtype == POLYGLOT_CUSTOM_TYPE_NAME { - let raw_name: String = upper(&match args.get("name") { - Some(value) => python_str(value), + if dtype == CUSTOM_TYPE_TAG { + let raw_name: String = upper(&match tree.get("name").and_then(Value::as_str) { + Some(name) => name.to_owned(), None => normalized_name.clone(), }) .replace(' ', ""); if snowflake && raw_name.starts_with("NUMBER") { - return Ok(snowflake_number(&raw_name)); + return snowflake_number(&raw_name); } - return Ok(normalize_with_fallback(&raw_name, dialect)); + return normalize_with_fallback(&raw_name, dialect); } if INTEGER_TYPE_NAMES.contains(&dtype) { if snowflake { - return Ok(snowflake_integer()); + return snowflake_integer(); } - return Ok(simple(&normalized_name, TypeFamily::Integer)); + return simple(&normalized_name, TypeFamily::Integer); } if DECIMAL_TYPE_NAMES.contains(&dtype) { let mut precision: Option = params.first().cloned(); @@ -102,16 +126,16 @@ pub(crate) fn normalize_parsed( scale = Some(INTEGER_SCALE.into()); name = format!("DECIMAL({INTEGER_PRECISION},{INTEGER_SCALE})"); } - return Ok(NormalizedType { + return NormalizedType { normalized_name: name, family: TypeFamily::Decimal, precision, scale, length: None, - }); + }; } if FLOAT_TYPE_NAMES.contains(&dtype) { - return Ok(simple(&normalized_name, TypeFamily::Float)); + return simple(&normalized_name, TypeFamily::Float); } if STRING_TYPE_NAMES.contains(&dtype) { let mut length: Option = params.first().cloned(); @@ -126,30 +150,44 @@ pub(crate) fn normalize_parsed( } else if length.is_some() && SIZED_TEXT_TYPE_NAMES.contains(&base_name) { name = name.replacen(base_name, VARCHAR_TYPE_NAME, 1); } - return Ok(NormalizedType { + return NormalizedType { normalized_name: name, family: TypeFamily::String, precision: None, scale: None, length, - }); + }; } if BOOLEAN_TYPE_NAMES.contains(&dtype) { - return Ok(simple(&normalized_name, TypeFamily::Boolean)); + return simple(&normalized_name, TypeFamily::Boolean); } if TIMESTAMP_TYPE_NAMES.contains(&dtype) { - return Ok(simple( + if bigquery && normalized_name == DATETIME_TYPE_NAME { + return simple(&normalized_name, TypeFamily::Datetime); + } + return simple( ×tamp_normalized_name(&normalized_name, dialect), TypeFamily::Timestamp, - )); + ); } if dtype == DATE_TYPE_NAME { - return Ok(simple(&normalized_name, TypeFamily::Date)); + return simple(&normalized_name, TypeFamily::Date); } if dtype == DATETIME_TYPE_NAME { - return Ok(simple(&normalized_name, TypeFamily::Datetime)); + return simple(&normalized_name, TypeFamily::Datetime); } - Ok(simple(&normalized_name, TypeFamily::Other)) + simple(&normalized_name, TypeFamily::Other) +} + +/// The scalar parameters of a type tree, keyed as the tree names them. +pub(crate) fn type_args(tree: &Value) -> serde_json::Map { + let mut args: serde_json::Map = serde_json::Map::new(); + for (key, value) in tree.as_object().into_iter().flatten() { + if !value.is_array() && !value.is_object() { + args.insert(key.clone(), value.clone()); + } + } + args } /// Snowflake's `NUMBER(p,s)` spelled as a custom type: Python's `DECIMAL` rewrite. @@ -172,57 +210,30 @@ fn snowflake_number(raw_name: &str) -> NormalizedType { } } -/// Python's `_polyglot_type_name`. -fn polyglot_type_name(args: &Map) -> String { - let data_type: String = upper(&match args.get("data_type") { - Some(value) => python_str(value), - None => String::new(), - }); - if let Some((_, alias)) = POLYGLOT_TYPE_NAME_ALIASES - .iter() - .find(|(name, _)| *name == data_type) - { +/// The type's tag, upper-cased with its word separators as the type names spell them. +fn type_name(tree: &Value) -> String { + let tag: String = upper(tree.get("data_type").and_then(Value::as_str).unwrap_or("")); + if let Some((_, alias)) = TYPE_TAG_ALIASES.iter().find(|(name, _)| *name == tag) { return (*alias).to_owned(); } - if data_type == POLYGLOT_CUSTOM_TYPE_NAME { - let name: String = upper(&match args.get("name") { - Some(value) => python_str(value), - None => String::new(), - }) - .replace(' ', ""); + if tag == CUSTOM_TYPE_TAG { + let name: String = + upper(tree.get("name").and_then(Value::as_str).unwrap_or("")).replace(' ', ""); if CUSTOM_NORMALIZATION_TYPE_NAMES.contains(&name.as_str()) { return name; } } - data_type + tag } -/// Python's `_polyglot_type_params`: `int(value)` of each parameter, skipping what it rejects. -fn polyglot_type_params(args: &Map) -> Vec { - let mut params: Vec = Vec::new(); - for key in ["precision", "scale", "length"] { - let Some(value) = args.get(key) else { - continue; - }; - let param: Option = match value { - Value::Null | Value::Array(_) | Value::Object(_) => None, - Value::Bool(flag) => Some(i64::from(*flag).into()), +/// The type's precision, scale and length, in that order, where it has them. +fn type_params(tree: &Value) -> Vec { + ["precision", "scale", "length"] + .iter() + .filter_map(|key| tree.get(*key)) + .filter_map(|value| match value { Value::Number(number) => python_int(&number.to_string()), - Value::String(text) => python_int(text), - }; - params.extend(param); - } - params -} - -/// Python's `str(value)` for the scalar values Polyglot gives a parsed type's tag and name. -fn python_str(value: &Value) -> String { - match value { - Value::String(text) => text.clone(), - Value::Null => "None".to_owned(), - Value::Bool(true) => "True".to_owned(), - Value::Bool(false) => "False".to_owned(), - Value::Number(number) => number.to_string(), - Value::Array(_) | Value::Object(_) => value.to_string(), - } + _ => None, + }) + .collect() } diff --git a/crates/sqlbuild-analysis/src/type_system/_helpers/shallow_args.rs b/crates/sqlbuild-analysis/src/type_system/_helpers/shallow_args.rs deleted file mode 100644 index 411f17c653..0000000000 --- a/crates/sqlbuild-analysis/src/type_system/_helpers/shallow_args.rs +++ /dev/null @@ -1,442 +0,0 @@ -//! The wheel's `DataType.args` scalars, read without walking nested types. - -use std::fmt::Display; - -use serde::ser::{ - Impossible, Serialize, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, - SerializeTuple, SerializeTupleStruct, SerializeTupleVariant, Serializer, -}; -use serde_json::{Map, Number, Value}; - -/// The top-level scalar fields of one serialized value; nested values are absent. -pub(crate) fn shallow_args(value: &T) -> Map { - value.serialize(Fields::default()).unwrap_or_default() -} - -#[derive(Debug)] -pub(crate) struct Unsupported; - -impl Display for Unsupported { - fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("not a struct") - } -} - -impl std::error::Error for Unsupported {} - -impl serde::ser::Error for Unsupported { - fn custom(_message: T) -> Self { - Self - } -} - -/// Collects one struct's scalar fields. -#[derive(Default)] -struct Fields { - fields: Map, -} - -impl SerializeStruct for Fields { - type Ok = Map; - type Error = Unsupported; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Unsupported> { - if let Some(scalar) = value.serialize(Scalar)? { - let _ = self.fields.insert(key.to_owned(), scalar); - } - Ok(()) - } - - fn end(self) -> Result, Unsupported> { - Ok(self.fields) - } -} - -impl SerializeStructVariant for Fields { - type Ok = Map; - type Error = Unsupported; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Unsupported> { - SerializeStruct::serialize_field(self, key, value) - } - - fn end(self) -> Result, Unsupported> { - Ok(self.fields) - } -} - -impl SerializeMap for Fields { - type Ok = Map; - type Error = Unsupported; - - fn serialize_key(&mut self, _key: &T) -> Result<(), Unsupported> { - Err(Unsupported) - } - - fn serialize_value(&mut self, _value: &T) -> Result<(), Unsupported> { - Err(Unsupported) - } - - fn end(self) -> Result, Unsupported> { - Ok(self.fields) - } -} - -macro_rules! not_a_struct { - ($($method:ident($($argument:ty),*)),* $(,)?) => { - $(fn $method(self, $(_: $argument),*) -> Result { - Err(Unsupported) - })* - }; -} - -impl Serializer for Fields { - type Ok = Map; - type Error = Unsupported; - type SerializeSeq = Impossible; - type SerializeTuple = Impossible; - type SerializeTupleStruct = Impossible; - type SerializeTupleVariant = Impossible; - type SerializeMap = Self; - type SerializeStruct = Self; - type SerializeStructVariant = Self; - - not_a_struct!( - serialize_bool(bool), - serialize_i8(i8), - serialize_i16(i16), - serialize_i32(i32), - serialize_i64(i64), - serialize_u8(u8), - serialize_u16(u16), - serialize_u32(u32), - serialize_u64(u64), - serialize_f32(f32), - serialize_f64(f64), - serialize_char(char), - serialize_str(&str), - serialize_bytes(&[u8]), - serialize_none(), - serialize_unit(), - serialize_unit_struct(&'static str), - serialize_unit_variant(&'static str, u32, &'static str), - ); - - fn serialize_some(self, value: &T) -> Result { - value.serialize(self) - } - - fn serialize_newtype_struct( - self, - _name: &'static str, - value: &T, - ) -> Result { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _name: &'static str, - _index: u32, - _variant: &'static str, - _value: &T, - ) -> Result { - Err(Unsupported) - } - - fn serialize_seq(self, _len: Option) -> Result { - Err(Unsupported) - } - - fn serialize_tuple(self, _len: usize) -> Result { - Err(Unsupported) - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Err(Unsupported) - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Err(Unsupported) - } - - fn serialize_map(self, _len: Option) -> Result { - Ok(self) - } - - fn serialize_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(self) - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Ok(self) - } -} - -/// One field's value if it is a scalar; nested values are skipped without visiting them. -struct Scalar; - -/// Swallows the children of a nested value unvisited. -struct Skipped; - -macro_rules! skip_children { - ($($trait:ident :: $method:ident),* $(,)?) => { - $(impl $trait for Skipped { - type Ok = Option; - type Error = Unsupported; - - fn $method(&mut self, _value: &T) -> Result<(), Unsupported> { - Ok(()) - } - - fn end(self) -> Result, Unsupported> { - Ok(None) - } - })* - }; -} - -skip_children!( - SerializeSeq::serialize_element, - SerializeTuple::serialize_element, - SerializeTupleStruct::serialize_field, - SerializeTupleVariant::serialize_field, -); - -impl SerializeMap for Skipped { - type Ok = Option; - type Error = Unsupported; - - fn serialize_key(&mut self, _key: &T) -> Result<(), Unsupported> { - Ok(()) - } - - fn serialize_value(&mut self, _value: &T) -> Result<(), Unsupported> { - Ok(()) - } - - fn end(self) -> Result, Unsupported> { - Ok(None) - } -} - -impl SerializeStruct for Skipped { - type Ok = Option; - type Error = Unsupported; - - fn serialize_field( - &mut self, - _key: &'static str, - _value: &T, - ) -> Result<(), Unsupported> { - Ok(()) - } - - fn end(self) -> Result, Unsupported> { - Ok(None) - } -} - -impl SerializeStructVariant for Skipped { - type Ok = Option; - type Error = Unsupported; - - fn serialize_field( - &mut self, - _key: &'static str, - _value: &T, - ) -> Result<(), Unsupported> { - Ok(()) - } - - fn end(self) -> Result, Unsupported> { - Ok(None) - } -} - -fn number(value: impl Into) -> Result, Unsupported> { - Ok(Some(Value::Number(value.into()))) -} - -impl Serializer for Scalar { - type Ok = Option; - type Error = Unsupported; - type SerializeSeq = Skipped; - type SerializeTuple = Skipped; - type SerializeTupleStruct = Skipped; - type SerializeTupleVariant = Skipped; - type SerializeMap = Skipped; - type SerializeStruct = Skipped; - type SerializeStructVariant = Skipped; - - fn serialize_bool(self, value: bool) -> Result { - Ok(Some(Value::Bool(value))) - } - - fn serialize_i8(self, value: i8) -> Result { - number(value) - } - - fn serialize_i16(self, value: i16) -> Result { - number(value) - } - - fn serialize_i32(self, value: i32) -> Result { - number(value) - } - - fn serialize_i64(self, value: i64) -> Result { - number(value) - } - - fn serialize_u8(self, value: u8) -> Result { - number(value) - } - - fn serialize_u16(self, value: u16) -> Result { - number(value) - } - - fn serialize_u32(self, value: u32) -> Result { - number(value) - } - - fn serialize_u64(self, value: u64) -> Result { - number(value) - } - - fn serialize_f32(self, value: f32) -> Result { - self.serialize_f64(f64::from(value)) - } - - fn serialize_f64(self, value: f64) -> Result { - Ok(Number::from_f64(value).map(Value::Number)) - } - - fn serialize_char(self, value: char) -> Result { - Ok(Some(Value::String(value.to_string()))) - } - - fn serialize_str(self, value: &str) -> Result { - Ok(Some(Value::String(value.to_owned()))) - } - - fn serialize_bytes(self, _value: &[u8]) -> Result { - Ok(None) - } - - fn serialize_none(self) -> Result { - Ok(Some(Value::Null)) - } - - fn serialize_some(self, value: &T) -> Result { - value.serialize(self) - } - - fn serialize_unit(self) -> Result { - Ok(Some(Value::Null)) - } - - fn serialize_unit_struct(self, _name: &'static str) -> Result { - Ok(Some(Value::Null)) - } - - fn serialize_unit_variant( - self, - _name: &'static str, - _index: u32, - variant: &'static str, - ) -> Result { - Ok(Some(Value::String(variant.to_owned()))) - } - - fn serialize_newtype_struct( - self, - _name: &'static str, - value: &T, - ) -> Result { - value.serialize(self) - } - - fn serialize_newtype_variant( - self, - _name: &'static str, - _index: u32, - _variant: &'static str, - _value: &T, - ) -> Result { - Ok(None) - } - - fn serialize_seq(self, _len: Option) -> Result { - Ok(Skipped) - } - - fn serialize_tuple(self, _len: usize) -> Result { - Ok(Skipped) - } - - fn serialize_tuple_struct( - self, - _name: &'static str, - _len: usize, - ) -> Result { - Ok(Skipped) - } - - fn serialize_tuple_variant( - self, - _name: &'static str, - _index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Ok(Skipped) - } - - fn serialize_map(self, _len: Option) -> Result { - Ok(Skipped) - } - - fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { - Ok(Skipped) - } - - fn serialize_struct_variant( - self, - _name: &'static str, - _index: u32, - _variant: &'static str, - _len: usize, - ) -> Result { - Ok(Skipped) - } -} diff --git a/crates/sqlbuild-analysis/src/type_system/_helpers/spelling.rs b/crates/sqlbuild-analysis/src/type_system/_helpers/spelling.rs new file mode 100644 index 0000000000..cf0b2056fa --- /dev/null +++ b/crates/sqlbuild-analysis/src/type_system/_helpers/spelling.rs @@ -0,0 +1,500 @@ +//! Each dialect's spelling of a type tree: the canonical text `same_type` compares. + +use serde_json::Value; + +/// A dialect whose type spellings SQLBuild knows. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SpellingDialect { + Generic, + Snowflake, + BigQuery, + DuckDb, + Databricks, + Postgres, + Tsql, + Sqlite, + ClickHouse, +} + +impl SpellingDialect { + /// The dialect a name refers to, in any letter case; None for a dialect SQLBuild lacks. + pub(crate) fn named(name: &str) -> Option { + Some(match name.to_ascii_lowercase().as_str() { + "" | "generic" => Self::Generic, + "snowflake" => Self::Snowflake, + "bigquery" => Self::BigQuery, + "duckdb" | "motherduck" => Self::DuckDb, + "databricks" => Self::Databricks, + "postgres" | "postgresql" => Self::Postgres, + "tsql" | "mssql" | "sqlserver" => Self::Tsql, + "sqlite" => Self::Sqlite, + "clickhouse" => Self::ClickHouse, + _ => return None, + }) + } +} + +/// `tree` written as `dialect` spells it. +pub(crate) fn spell(tree: &Value, dialect: SpellingDialect) -> String { + let mut text: String = String::new(); + Speller { + dialect, + text: &mut text, + } + .data_type(tree, false); + text +} + +struct Speller<'a> { + dialect: SpellingDialect, + text: &'a mut String, +} + +impl Speller<'_> { + fn write(&mut self, text: &str) { + self.text.push_str(text); + } + + /// `name`, then `(length)` when there is one. + fn sized(&mut self, name: &str, length: Option) { + self.write(name); + if let Some(length) = length { + self.write(&format!("({length})")); + } + } + + /// `name`, then `(precision)` or `(precision, scale)` when there is a precision. + fn scaled(&mut self, name: &str, precision: Option, scale: Option) { + self.write(name); + if let Some(precision) = precision { + match scale { + Some(scale) => self.write(&format!("({precision}, {scale})")), + None => self.write(&format!("({precision})")), + } + } + } + + /// A ClickHouse scalar, nullable except as a map key. + fn clickhouse(&mut self, name: &str, map_key: bool) { + if map_key { + self.write(name); + } else { + self.write(&format!("Nullable({name})")); + } + } + + fn data_type(&mut self, tree: &Value, map_key: bool) { + let tag: &str = tree.get("data_type").and_then(Value::as_str).unwrap_or(""); + if self.number_type(tree, tag, map_key) + || self.text_type(tree, tag, map_key) + || self.time_type(tree, tag) + { + return; + } + self.other_type(tree, tag); + } + + /// A boolean or numeric type; false for any other tag. + fn number_type(&mut self, tree: &Value, tag: &str, map_key: bool) -> bool { + use SpellingDialect::{ + BigQuery, ClickHouse, Databricks, Postgres, Snowflake, Sqlite, Tsql, + }; + let dialect: SpellingDialect = self.dialect; + let number = |key: &str| tree.get(key).and_then(Value::as_u64); + let flag = |key: &str| tree.get(key).and_then(Value::as_bool).unwrap_or(false); + let length: Option = number("length"); + let precision: Option = number("precision"); + let scale: Option = number("scale"); + match tag { + "boolean" => match dialect { + Tsql => self.write("BIT"), + ClickHouse => self.write("Bool"), + _ => self.write("BOOLEAN"), + }, + "tiny_int" => match dialect { + Postgres => self.sized("SMALLINT", length), + ClickHouse => self.clickhouse("Int8", map_key), + _ => self.sized("TINYINT", length), + }, + "small_int" => match dialect { + Sqlite => self.write("INTEGER"), + BigQuery => self.write("INT64"), + ClickHouse => self.clickhouse("Int16", map_key), + _ => self.sized("SMALLINT", length), + }, + "int" => match dialect { + BigQuery => self.write("INT64"), + ClickHouse => self.clickhouse("Int32", map_key), + Tsql | Sqlite => self.sized("INTEGER", length), + _ => self.sized("INT", length), + }, + "big_int" => match dialect { + BigQuery => self.write("INT64"), + ClickHouse => self.clickhouse("Int64", map_key), + _ => self.sized("BIGINT", length), + }, + "int128" => match dialect { + ClickHouse => self.write("Int128"), + _ => self.write("INT128"), + }, + "uint8" | "uint16" | "uint32" | "uint64" | "uint128" => { + let (canonical, clickhouse) = match tag { + "uint8" => ("UTINYINT", "UInt8"), + "uint16" => ("USMALLINT", "UInt16"), + "uint32" => ("UINTEGER", "UInt32"), + "uint64" => ("UBIGINT", "UInt64"), + _ => ("UINT128", "UInt128"), + }; + self.write(if dialect == ClickHouse { + clickhouse + } else { + canonical + }); + } + "float" if dialect == ClickHouse => { + self.clickhouse("Float32", map_key); + self.scaled("", precision, scale); + } + "double" if dialect == ClickHouse => { + self.clickhouse("Float64", map_key); + self.scaled("", precision, scale); + } + "float" => { + let name: &str = if flag("real_spelling") + && !matches!(dialect, Databricks | Snowflake | BigQuery) + { + "REAL" + } else { + match dialect { + Postgres => "REAL", + BigQuery => "FLOAT64", + _ => "FLOAT", + } + }; + if dialect == Databricks { + self.write(name); + } else { + self.scaled(name, precision, scale); + } + } + "double" => { + let name: &str = match dialect { + Tsql => "FLOAT", + BigQuery => "FLOAT64", + Sqlite => "REAL", + Postgres => "DOUBLE PRECISION", + _ => "DOUBLE", + }; + self.scaled(name, precision, scale); + } + "decimal" => match dialect { + ClickHouse => self.scaled("Decimal", precision, scale), + BigQuery => self.scaled("NUMERIC", precision, scale), + _ => self.scaled("DECIMAL", precision, scale), + }, + _ => return false, + } + true + } + + /// A text, binary, JSON or UUID type; false for any other tag. + fn text_type(&mut self, tree: &Value, tag: &str, map_key: bool) -> bool { + use SpellingDialect::{ + BigQuery, ClickHouse, Databricks, DuckDb, Postgres, Snowflake, Sqlite, Tsql, + }; + let dialect: SpellingDialect = self.dialect; + let number = |key: &str| tree.get(key).and_then(Value::as_u64); + let length: Option = number("length"); + match tag { + "text_with_length" if dialect == ClickHouse => self.clickhouse("String", map_key), + "char" | "var_char" | "string" | "text" if dialect == ClickHouse => { + self.write("String"); + } + "char" => match dialect { + DuckDb | Sqlite => self.write("TEXT"), + Databricks if length.is_none() => self.write("STRING"), + _ => self.sized("CHAR", length), + }, + "var_char" => match dialect { + DuckDb | Sqlite => self.sized("TEXT", length), + Databricks if length.is_none() => self.write("STRING"), + _ => self.sized("VARCHAR", length), + }, + "text" => match dialect { + Tsql => self.write("VARCHAR(MAX)"), + BigQuery | Databricks => self.write("STRING"), + Snowflake => self.write("VARCHAR"), + _ => self.write("TEXT"), + }, + "text_with_length" => match dialect { + Databricks | Snowflake | Tsql => self.sized("VARCHAR", length), + BigQuery => self.sized("STRING", length), + _ => self.sized("TEXT", length), + }, + "string" => match dialect { + Postgres if length.is_none() => self.write("TEXT"), + Postgres => self.sized("VARCHAR", length), + Tsql if length.is_none() => self.write("VARCHAR(MAX)"), + Tsql => self.sized("VARCHAR", length), + DuckDb => self.sized("TEXT", length), + _ => self.sized("STRING", length), + }, + "binary" | "var_binary" if dialect == BigQuery => self.sized("BYTES", length), + "binary" => match dialect { + Postgres => self.sized("BYTEA", length), + DuckDb | Sqlite => self.sized("BLOB", length), + ClickHouse => self.sized("FixedString", length), + _ => self.sized("BINARY", length), + }, + "var_binary" => match dialect { + Postgres => self.sized("BYTEA", length), + DuckDb | Sqlite => self.sized("BLOB", length), + Databricks => self.sized("BINARY", length), + ClickHouse => self.clickhouse("String", map_key), + _ => self.sized("VARBINARY", length), + }, + "blob" => match dialect { + Postgres => self.write("BYTEA"), + Tsql | DuckDb => self.write("VARBINARY"), + BigQuery => self.write("BYTES"), + Databricks => self.write("BINARY"), + ClickHouse => self.clickhouse("String", map_key), + _ => self.write("BLOB"), + }, + "bit" => match dialect { + Databricks | Snowflake | BigQuery | ClickHouse => self.write("BOOLEAN"), + _ => self.sized("BIT", length), + }, + "var_bit" => self.sized("VARBIT", length), + "json_b" if dialect == Postgres => self.write("JSONB"), + "json" | "json_b" => match dialect { + Tsql => self.write("NVARCHAR(MAX)"), + Snowflake => self.write("VARIANT"), + _ => self.write("JSON"), + }, + "uuid" => match dialect { + Tsql => self.write("UNIQUEIDENTIFIER"), + BigQuery | Databricks => self.write("STRING"), + _ => self.write("UUID"), + }, + _ => return false, + } + true + } + + /// A date, time, timestamp or interval type; false for any other tag. + fn time_type(&mut self, tree: &Value, tag: &str) -> bool { + use SpellingDialect::{BigQuery, ClickHouse, Databricks, DuckDb, Postgres, Tsql}; + let dialect: SpellingDialect = self.dialect; + let number = |key: &str| tree.get(key).and_then(Value::as_u64); + let flag = |key: &str| tree.get(key).and_then(Value::as_bool).unwrap_or(false); + let precision: Option = number("precision"); + match tag { + "date" => self.write("DATE"), + "time" if flag("timezone") => match dialect { + DuckDb => self.write("TIMETZ"), + Postgres => self.sized("TIMETZ", precision), + _ => { + self.sized("TIME", precision); + self.write(" WITH TIME ZONE"); + } + }, + "time" => match dialect { + Databricks => self.write("TIMESTAMP"), + _ => self.sized("TIME", precision), + }, + "timestamp" => { + let timezone: bool = flag("timezone"); + match dialect { + ClickHouse => self.sized("DateTime", precision), + Tsql if timezone => self.sized("DATETIMEOFFSET", precision), + Tsql => self.sized("DATETIME2", precision), + BigQuery if timezone => self.write("TIMESTAMP"), + BigQuery => self.write("DATETIME"), + DuckDb if timezone => self.write("TIMESTAMPTZ"), + _ if timezone => self.sized("TIMESTAMPTZ", precision), + _ => self.sized("TIMESTAMP", precision), + } + } + "interval" => { + self.write("INTERVAL"); + for (key, prefix) in [("unit", " "), ("to", " TO ")] { + if let Some(unit) = tree.get(key).and_then(Value::as_str) { + self.write(prefix); + self.write(&unit.to_ascii_uppercase()); + } + } + } + _ => return false, + } + true + } + + /// A nested, custom or other type. + fn other_type(&mut self, tree: &Value, tag: &str) { + use SpellingDialect::{ClickHouse, DuckDb, Generic, Postgres, Snowflake, Tsql}; + let dialect: SpellingDialect = self.dialect; + let number = |key: &str| tree.get(key).and_then(Value::as_u64); + match tag { + "nullable" => { + self.write("Nullable("); + self.data_type(tree.get("inner_type").unwrap_or(&Value::Null), true); + self.write(")"); + } + "array" => { + let element: &Value = tree.get("element_type").unwrap_or(&Value::Null); + match dialect { + Postgres | DuckDb => { + self.data_type(element, false); + match number("dimension") { + Some(dimension) => self.write(&format!("[{dimension}]")), + None => self.write("[]"), + } + } + Snowflake | ClickHouse => { + self.write(if dialect == ClickHouse { + "Array(" + } else { + "ARRAY(" + }); + self.data_type(element, false); + self.write(")"); + } + Tsql => self.write("NVARCHAR(MAX)"), + _ => { + self.write("ARRAY<"); + self.data_type(element, false); + self.write(">"); + } + } + } + "list" => { + self.data_type(tree.get("element_type").unwrap_or(&Value::Null), false); + self.write(" LIST"); + } + "map" => { + let key: &Value = tree.get("key_type").unwrap_or(&Value::Null); + let value: &Value = tree.get("value_type").unwrap_or(&Value::Null); + let (open, close) = match dialect { + Snowflake | DuckDb => ("MAP(", ")"), + ClickHouse => ("Map(", ")"), + _ => ("MAP<", ">"), + }; + self.write(open); + self.data_type(key, dialect == ClickHouse); + self.write(", "); + self.data_type(value, false); + self.write(close); + } + "struct" => self.struct_type(tree), + "object" => self.field_list("OBJECT(", tree), + "union" => self.field_list("UNION(", tree), + "vector" => { + self.write("VECTOR"); + let element: Option<&Value> = tree.get("element_type").filter(|v| !v.is_null()); + let dimension: Option = number("dimension"); + if element.is_some() || dimension.is_some() { + self.write("("); + if let Some(element) = element { + self.data_type(element, false); + if dimension.is_some() { + self.write(", "); + } + } + if let Some(dimension) = dimension { + self.write(&dimension.to_string()); + } + self.write(")"); + } + } + "enum" => { + let values: Vec = tree + .get("values") + .and_then(Value::as_array) + .map_or(&[][..], Vec::as_slice) + .iter() + .map(|value| format!("'{}'", value.as_str().unwrap_or("").replace('\'', "''"))) + .collect(); + self.write(&format!("ENUM({})", values.join(", "))); + } + "custom" => { + let name: &str = tree.get("name").and_then(Value::as_str).unwrap_or(""); + self.write(&custom_name(name, dialect)); + } + _ if dialect == Generic => self.write(&tag.to_ascii_uppercase()), + _ => self.write(&tag.to_ascii_uppercase().replace('_', "")), + } + } + + /// An `OBJECT` or `UNION` of named fields, opened by `open`. + fn field_list(&mut self, open: &str, tree: &Value) { + self.write(open); + let fields: &[Value] = tree + .get("fields") + .and_then(Value::as_array) + .map_or(&[], Vec::as_slice); + for (index, field) in fields.iter().enumerate() { + if index > 0 { + self.write(", "); + } + self.write(field.get(0).and_then(Value::as_str).unwrap_or("")); + self.write(" "); + self.data_type(field.get(1).unwrap_or(&Value::Null), false); + } + self.write(")"); + } + + fn struct_type(&mut self, tree: &Value) { + use SpellingDialect::{BigQuery, ClickHouse, Databricks, DuckDb, Snowflake}; + let dialect: SpellingDialect = self.dialect; + let nested: bool = tree.get("nested").and_then(Value::as_bool).unwrap_or(false); + let (open, close, separator): (&str, &str, &str) = match dialect { + Snowflake => ("OBJECT(", ")", " "), + DuckDb => ("STRUCT(", ")", " "), + ClickHouse => ("Tuple(", ")", " "), + Databricks | BigQuery => ( + "STRUCT<", + ">", + if dialect == Databricks { ": " } else { " " }, + ), + _ if nested => ("STRUCT(", ")", " "), + _ => ("STRUCT<", ">", " "), + }; + self.write(open); + let fields: &[Value] = tree + .get("fields") + .and_then(Value::as_array) + .map_or(&[], Vec::as_slice); + for (index, field) in fields.iter().enumerate() { + if index > 0 { + self.write(", "); + } + let name: &str = field.get("name").and_then(Value::as_str).unwrap_or(""); + if !name.is_empty() { + self.write(name); + self.write(separator); + } + self.data_type(field.get("data_type").unwrap_or(&Value::Null), false); + } + self.write(close); + } +} + +/// A type name no dialect gives its own shape, as `dialect` spells it. +fn custom_name(name: &str, dialect: SpellingDialect) -> String { + let (base, arguments): (&str, &str) = name.find('(').map_or((name, ""), |at| name.split_at(at)); + let base_upper: String = base.trim().to_ascii_uppercase(); + match (dialect, base_upper.as_str()) { + (SpellingDialect::Snowflake, "TIMESTAMP_NTZ" | "TIMESTAMP_LTZ" | "TIMESTAMP_TZ") => { + format!("{}{arguments}", base_upper.replace('_', "")) + } + (SpellingDialect::Tsql, "VARIANT") => "SQL_VARIANT".to_owned(), + ( + SpellingDialect::BigQuery | SpellingDialect::ClickHouse | SpellingDialect::Snowflake, + _, + ) => name.to_owned(), + (_, "INT64") if arguments.is_empty() => "BIGINT".to_owned(), + (_, "FLOAT64") if arguments.is_empty() => "DOUBLE".to_owned(), + _ => name.to_owned(), + } +} diff --git a/crates/sqlbuild-analysis/src/type_system/_helpers/type_reader.rs b/crates/sqlbuild-analysis/src/type_system/_helpers/type_reader.rs new file mode 100644 index 0000000000..4ee2951ea5 --- /dev/null +++ b/crates/sqlbuild-analysis/src/type_system/_helpers/type_reader.rs @@ -0,0 +1,542 @@ +//! SQLBuild's own type reader, which builds the tree SQLQuack's type parsers build. + +use serde_json::{Map, Value, json}; + +use crate::type_system::_helpers::spelling::SpellingDialect; + +/// The type tree of `type_sql`, or why the reader rejects it. +pub(crate) fn read_type(type_sql: &str, dialect: SpellingDialect) -> Result { + let tokens: Vec = tokens(type_sql, dialect)?; + let mut reader = Reader { + tokens: &tokens, + position: 0, + dialect, + }; + let tree: Value = reader.data_type()?; + if reader.peek() == Some(&Token::Punct(';')) { + reader.position += 1; + } + match reader.peek() { + None => Ok(tree), + Some(token) => Err(format!("unexpected {} after the type", token.text())), + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum Token { + Word(String), + Quoted(String), + /// A single-quoted string, such as a ClickHouse enum member or time zone. + Literal(String), + Number(String), + Punct(char), +} + +impl Token { + fn text(&self) -> String { + match self { + Self::Word(text) | Self::Quoted(text) | Self::Literal(text) | Self::Number(text) => { + format!("'{text}'") + } + Self::Punct(character) => format!("'{character}'"), + } + } + + /// The token as a type name writes it. + fn source(&self) -> String { + match self { + Self::Word(text) | Self::Number(text) => text.clone(), + Self::Quoted(text) => format!("\"{text}\""), + Self::Literal(text) => format!("'{}'", text.replace('\'', "''")), + Self::Punct(character) => character.to_string(), + } + } +} + +fn tokens(type_sql: &str, dialect: SpellingDialect) -> Result, String> { + let characters: Vec = type_sql.chars().collect(); + let mut tokens: Vec = Vec::new(); + let mut index: usize = 0; + while let Some(&character) = characters.get(index) { + if character.is_whitespace() { + index += 1; + } else if character.is_alphabetic() || character == '_' { + let start: usize = index; + while characters + .get(index) + .is_some_and(|next| next.is_alphanumeric() || *next == '_' || *next == '$') + { + index += 1; + } + tokens.push(Token::Word(characters[start..index].iter().collect())); + } else if character.is_ascii_digit() { + let start: usize = index; + while characters.get(index).is_some_and(char::is_ascii_digit) { + index += 1; + } + if characters + .get(index) + .is_some_and(|next| next.is_alphabetic() || *next == '_') + { + return Err(format!("invalid identifier at offset {start}")); + } + tokens.push(Token::Number(characters[start..index].iter().collect())); + } else if let Some(close) = quote_close(character, dialect) { + let start: usize = index + 1; + index = start; + while characters.get(index).is_some_and(|next| *next != close) { + index += 1; + } + if index >= characters.len() { + return Err("unterminated quoted name".to_owned()); + } + tokens.push(Token::Quoted(characters[start..index].iter().collect())); + index += 1; + } else if character == '\'' && dialect == SpellingDialect::ClickHouse { + let (literal, end) = literal(&characters, index)?; + tokens.push(Token::Literal(literal)); + index = end; + } else if "()<>,[]:;".contains(character) + || ("=-".contains(character) && dialect == SpellingDialect::ClickHouse) + { + tokens.push(Token::Punct(character)); + index += 1; + } else { + return Err(format!("unexpected character '{character}'")); + } + } + Ok(tokens) +} + +/// The single-quoted string opening at `start`, with `''` and backslash escapes, and the index after it. +fn literal(characters: &[char], start: usize) -> Result<(String, usize), String> { + let mut text: String = String::new(); + let mut index: usize = start + 1; + loop { + match characters.get(index) { + None => return Err("unterminated string".to_owned()), + Some('\\') => { + text.extend(characters.get(index + 1)); + index += 2; + } + Some('\'') if characters.get(index + 1) == Some(&'\'') => { + text.push('\''); + index += 2; + } + Some('\'') => return Ok((text, index + 1)), + Some(&character) => { + text.push(character); + index += 1; + } + } + } +} + +/// The closing quote of a quoted name `open` starts in `dialect`. +fn quote_close(open: char, dialect: SpellingDialect) -> Option { + let backtick: bool = matches!( + dialect, + SpellingDialect::Databricks | SpellingDialect::BigQuery | SpellingDialect::ClickHouse + ); + match open { + '`' if backtick => Some('`'), + '"' if !matches!( + dialect, + SpellingDialect::Databricks | SpellingDialect::BigQuery + ) => + { + Some('"') + } + '[' if dialect == SpellingDialect::Tsql => Some(']'), + _ => None, + } +} + +struct Reader<'a> { + tokens: &'a [Token], + position: usize, + dialect: SpellingDialect, +} + +impl Reader<'_> { + fn peek(&self) -> Option<&Token> { + self.tokens.get(self.position) + } + + fn next(&mut self) -> Option { + let token: Option = self.peek().cloned(); + self.position += usize::from(token.is_some()); + token + } + + fn eat_punct(&mut self, character: char) -> bool { + let found: bool = self.peek() == Some(&Token::Punct(character)); + self.position += usize::from(found); + found + } + + fn expect_punct(&mut self, character: char) -> Result<(), String> { + if self.eat_punct(character) { + return Ok(()); + } + Err(match self.peek() { + Some(token) => format!("expected '{character}', found {}", token.text()), + None => format!("expected '{character}' at the end of the type"), + }) + } + + fn eat_word(&mut self, word: &str) -> bool { + let found: bool = + matches!(self.peek(), Some(Token::Word(text)) if text.eq_ignore_ascii_case(word)); + self.position += usize::from(found); + found + } + + fn data_type(&mut self) -> Result { + let mut tree: Value = self.base_type()?; + while self.eat_punct('[') { + let dimension: Option = match self.peek() { + Some(Token::Number(_)) => Some(self.number()?), + _ => None, + }; + self.expect_punct(']')?; + let mut array: Map = Map::new(); + array.insert("data_type".to_owned(), json!("array")); + array.insert("element_type".to_owned(), tree); + if let Some(dimension) = dimension { + array.insert("dimension".to_owned(), json!(dimension)); + } + tree = Value::Object(array); + } + Ok(tree) + } + + fn base_type(&mut self) -> Result { + match self.next() { + Some(Token::Quoted(name)) => self.custom(name), + Some(Token::Word(word)) => self.named_type(&word), + other => Err(unexpected("a type name", other.as_ref())), + } + } + + fn named_type(&mut self, word: &str) -> Result { + let upper: String = word.to_ascii_uppercase(); + match upper.as_str() { + "ARRAY" => { + let close: char = self.open_nested()?; + let element: Value = self.data_type()?; + self.expect_punct(close)?; + Ok(json!({"data_type": "array", "element_type": element})) + } + "NULLABLE" if self.dialect == SpellingDialect::ClickHouse => self.nullable(), + "MAP" => { + let close: char = self.open_nested()?; + let key: Value = self.data_type()?; + self.expect_punct(',')?; + let value: Value = self.data_type()?; + self.expect_punct(close)?; + Ok(json!({"data_type": "map", "key_type": key, "value_type": value})) + } + "STRUCT" => { + let close: char = self.open_nested()?; + let mut fields: Vec = Vec::new(); + loop { + let name: String = match self.next() { + Some(Token::Word(name) | Token::Quoted(name)) => name, + _ => return Err("expected a struct field name".to_owned()), + }; + self.eat_punct(':'); + let data_type: Value = self.data_type()?; + fields.push(json!({"name": name, "data_type": data_type})); + if !self.eat_punct(',') { + break; + } + } + self.expect_punct(close)?; + Ok(json!({"data_type": "struct", "fields": fields, "nested": close == ')'})) + } + "DOUBLE" => { + self.eat_word("PRECISION"); + let (precision, scale) = self.precision_scale()?; + Ok(json!({"data_type": "double", "precision": precision, "scale": scale})) + } + "CHARACTER" | "CHAR" if self.eat_word("VARYING") => { + let length: Option = self.text_length(Self::length)?; + Ok(json!({"data_type": "var_char", "length": length})) + } + "TIMESTAMP" => self.time_type("timestamp", self.dialect == SpellingDialect::BigQuery), + "TIME" => self.time_type("time", false), + _ => self.scalar_type(word, &upper), + } + } + + /// ClickHouse's `Nullable(T)`, which wraps one scalar type: not another Nullable or a nested type. + fn nullable(&mut self) -> Result { + self.expect_punct('(')?; + let inner: Value = self.data_type()?; + self.expect_punct(')')?; + match inner.get("data_type").and_then(Value::as_str) { + Some(tag @ ("nullable" | "array" | "map" | "struct")) => { + Err(format!("Nullable cannot wrap the {tag} type")) + } + _ => Ok(json!({"data_type": "nullable", "inner_type": inner})), + } + } + + /// A time or timestamp; `zoned` when the bare type has a time zone, as in BigQuery. + fn time_type(&mut self, tag: &str, zoned: bool) -> Result { + let precision: Option = self.length()?; + let timezone: bool = self.time_zone()?.unwrap_or(zoned); + Ok(json!({"data_type": tag, "precision": precision, "timezone": timezone})) + } + + /// The opening bracket of a nested type's parameters, returning its closing bracket. + fn open_nested(&mut self) -> Result { + if self.eat_punct('<') { + return Ok('>'); + } + self.expect_punct('(')?; + Ok(')') + } + + /// `WITH TIME ZONE` or `WITHOUT TIME ZONE` after a time type, None when neither follows. + fn time_zone(&mut self) -> Result, String> { + let timezone: bool = if self.eat_word("WITH") { + true + } else if self.eat_word("WITHOUT") { + false + } else { + return Ok(None); + }; + if self.eat_word("TIME") && self.eat_word("ZONE") { + return Ok(Some(timezone)); + } + Err("expected TIME ZONE".to_owned()) + } + + fn scalar_type(&mut self, word: &str, upper: &str) -> Result { + let tagged = |tag: &str, length: Option| json!({"data_type": tag, "length": length}); + Ok(match upper { + "INT" | "INTEGER" => tagged("int", self.sized_length()?), + "BIGINT" => tagged("big_int", self.sized_length()?), + "SMALLINT" => tagged("small_int", self.sized_length()?), + "TINYINT" => tagged("tiny_int", self.sized_length()?), + "VARCHAR" => { + if self.max_length() { + return Ok(json!({"data_type": "custom", "name": upper})); + } + tagged("var_char", self.text_length(Self::sized_length)?) + } + "CHAR" | "CHARACTER" => tagged("char", self.text_length(Self::sized_length)?), + "STRING" => tagged("string", self.text_length(Self::length)?), + "BINARY" => { + let length: Option = self.length()?; + if length.is_none() && self.dialect == SpellingDialect::ClickHouse { + return Err("ClickHouse's BINARY needs a length".to_owned()); + } + tagged("binary", length) + } + "GEOGRAPHY" if self.single_geography() => { + if !self.max_length() { + self.length()?; + } + json!({"data_type": "custom", "name": upper}) + } + "VARBINARY" | "NVARCHAR" | "GEOGRAPHY" if self.max_length() => { + json!({"data_type": "custom", "name": format!("{upper}(MAX)")}) + } + "VARBINARY" => tagged("var_binary", self.length()?), + "TEXT" | "NTEXT" => match self.length()? { + Some(length) => json!({"data_type": "text_with_length", "length": length}), + None => json!({"data_type": "text"}), + }, + "DECIMAL" | "NUMERIC" => { + let (precision, scale) = self.precision_scale()?; + json!({"data_type": "decimal", "precision": precision, "scale": scale}) + } + "FLOAT" => self.float_type(false)?, + "REAL" => self.float_type(true)?, + "TIMESTAMPTZ" | "TIMESTAMP_TZ" => { + let precision: Option = self.length()?; + json!({"data_type": "timestamp", "precision": precision, "timezone": true}) + } + "DATETIME" if self.max_length() => json!({"data_type": "custom", "name": upper}), + "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" | "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" | "DATETIME" => { + let _precision: Option = self.length()?; + json!({"data_type": "custom", "name": upper.replace('_', "")}) + } + "BOOL" | "BOOLEAN" => json!({"data_type": "boolean"}), + "DATE" => json!({"data_type": "date"}), + "INTERVAL" => json!({"data_type": "interval"}), + "JSON" => json!({"data_type": "json"}), + "JSONB" => json!({"data_type": "json_b"}), + "UUID" => json!({"data_type": "uuid"}), + "BLOB" => json!({"data_type": "blob"}), + "HUGEINT" => json!({"data_type": "int128"}), + "LONG" => json!({"data_type": "custom", "name": upper}), + "NVARCHAR" | "GEOGRAPHY" => { + let length: Option = self.length()?; + let name: String = match length { + Some(length) => format!("{upper}({length})"), + None => upper.to_owned(), + }; + json!({"data_type": "custom", "name": name}) + } + _ => return self.custom(word.to_owned()), + }) + } + + /// Whether the dialect has one GEOGRAPHY, which takes no parameters: BigQuery and Snowflake. + fn single_geography(&self) -> bool { + matches!( + self.dialect, + SpellingDialect::BigQuery | SpellingDialect::Snowflake + ) + } + + /// A FLOAT or REAL; Postgres's REAL is a 4-byte float, whatever precision follows it. + fn float_type(&mut self, real: bool) -> Result { + let (mut precision, mut scale) = self.precision_scale()?; + if real && self.dialect == SpellingDialect::Postgres { + (precision, scale) = (None, None); + } + Ok(json!({ + "data_type": "float", + "precision": precision, + "scale": scale, + "real_spelling": real, + })) + } + + /// Any other type name with any number of integer parameters, kept as written. + fn custom(&mut self, name: String) -> Result { + if !self.eat_punct('(') { + return Ok(json!({"data_type": "custom", "name": name})); + } + if self.eat_punct(')') { + return Ok(json!({"data_type": "custom", "name": format!("{name}()")})); + } + let mut parameters: Vec = Vec::new(); + loop { + match self.dialect { + SpellingDialect::ClickHouse => parameters.push(self.clickhouse_parameter(&name)?), + _ => match self.next() { + Some(Token::Number(number)) => parameters.push(number), + Some(Token::Word(word)) => parameters.push(word), + _ => return Err(format!("expected a parameter of {name}")), + }, + } + if !self.eat_punct(',') { + break; + } + } + self.expect_punct(')')?; + Ok(json!({"data_type": "custom", "name": format!("{name}({})", parameters.join(", "))})) + } + + /// One ClickHouse type parameter as written: a nested type, a named tuple field or an enum member. + fn clickhouse_parameter(&mut self, name: &str) -> Result { + let mut text: String = String::new(); + let mut depth: usize = 0; + let mut spaced: bool = false; + loop { + let token: Token = match self.peek() { + None => return Err(format!("expected ')' after the parameters of {name}")), + Some(Token::Punct(',' | ')')) if depth == 0 => break, + Some(token) => token.clone(), + }; + self.position += 1; + let word: bool = !matches!(token, Token::Punct(_)); + match token { + Token::Punct('(') => depth += 1, + Token::Punct(')') => depth -= 1, + _ => {} + } + if token == Token::Punct('=') { + text.push_str(" = "); + spaced = false; + continue; + } + if word && spaced { + text.push(' '); + } + text.push_str(&token.source()); + spaced = word; + } + if text.is_empty() { + return Err(format!("expected a parameter of {name}")); + } + Ok(text) + } + + /// The optional length of an integer or character type; empty parentheses state none. + fn sized_length(&mut self) -> Result, String> { + if self.peek() == Some(&Token::Punct('(')) + && self.tokens.get(self.position + 1) == Some(&Token::Punct(')')) + { + self.position += 2; + return Ok(None); + } + self.length() + } + + /// A character type's length, which ClickHouse reads and drops: its `String` has none. + fn text_length( + &mut self, + read: fn(&mut Self) -> Result, String>, + ) -> Result, String> { + let length: Option = read(self)?; + Ok(length.filter(|_| self.dialect != SpellingDialect::ClickHouse)) + } + + /// A parenthesized `MAX`, which only the variable-length types take. + fn max_length(&mut self) -> bool { + let found: bool = self.peek() == Some(&Token::Punct('(')) + && matches!(self.tokens.get(self.position + 1), Some(Token::Word(word)) if word.eq_ignore_ascii_case("MAX")) + && self.tokens.get(self.position + 2) == Some(&Token::Punct(')')); + self.position += if found { 3 } else { 0 }; + found + } + + /// One optional parenthesized integer. + fn length(&mut self) -> Result, String> { + if !self.eat_punct('(') { + return Ok(None); + } + let length: u32 = self.number()?; + self.expect_punct(')')?; + Ok(Some(length)) + } + + /// An optional parenthesized precision and scale. + fn precision_scale(&mut self) -> Result<(Option, Option), String> { + if !self.eat_punct('(') { + return Ok((None, None)); + } + let precision: u32 = self.number()?; + let scale: Option = if self.eat_punct(',') { + Some(self.number()?) + } else { + None + }; + self.expect_punct(')')?; + Ok((Some(precision), scale)) + } + + fn number(&mut self) -> Result { + match self.next() { + Some(Token::Number(number)) => number + .parse::() + .map_err(|_| format!("invalid number: {number}")), + other => Err(unexpected("a number", other.as_ref())), + } + } +} + +/// Why `found`, or the end of the type, is not the `expected` token. +fn unexpected(expected: &str, found: Option<&Token>) -> String { + match found { + Some(token) => format!("expected {expected}, found {}", token.text()), + None => format!("expected {expected}"), + } +} diff --git a/crates/sqlbuild-analysis/src/type_system/constants.rs b/crates/sqlbuild-analysis/src/type_system/constants.rs index e5d79cc893..8fd1b089fb 100644 --- a/crates/sqlbuild-analysis/src/type_system/constants.rs +++ b/crates/sqlbuild-analysis/src/type_system/constants.rs @@ -9,7 +9,14 @@ pub(crate) const FLOAT_TYPE_NAMES: &[&str] = &["DOUBLE", "FLOAT", "FLOAT64", "RE pub(crate) const INTEGER_TYPE_NAMES: &[&str] = &[ "BIGINT", "INT", "INT64", "INTEGER", "LONG", "SMALLINT", "TINYINT", ]; -pub(crate) const POLYGLOT_CUSTOM_TYPE_NAME: &str = "CUSTOM"; +/// The deepest type nesting SQLBuild reads: SQLQuack's syntax tree depth. +pub(crate) const MAX_TYPE_NESTING: usize = 512; +/// The type nesting read on the caller's stack; deeper types get their own. +pub(crate) const SHALLOW_TYPE_NESTING: usize = 32; +/// The stack a more deeply nested type is read and normalized on. +pub(crate) const DEEP_TYPE_STACK_BYTES: usize = 32 * 1024 * 1024; +/// The tag of a type name no dialect gives its own shape. +pub(crate) const CUSTOM_TYPE_TAG: &str = "CUSTOM"; pub(crate) const STRING_TYPE_NAMES: &[&str] = &["CHAR", "CHARACTER", "STRING", "TEXT", "VARCHAR"]; pub(crate) const TIMESTAMP_TYPE_NAMES: &[&str] = &["TIMESTAMP", "TIMESTAMPLTZ", "TIMESTAMPNTZ", "TIMESTAMPTZ"]; @@ -31,16 +38,14 @@ pub(crate) const SIZED_TEXT_TYPE_NAMES: &[&str] = &["STRING", "TEXT"]; pub(crate) const VARCHAR_TYPE_NAME: &str = "VARCHAR"; /// BigQuery's one spelling of every variable-length text type. pub(crate) const BIGQUERY_STRING_TYPE_NAME: &str = "STRING"; -/// Polyglot `data_type` tags whose Python spelling drops the word separator. -pub(crate) const POLYGLOT_TYPE_NAME_ALIASES: &[(&str, &str)] = &[ +/// Type tags whose type name drops the word separator. +pub(crate) const TYPE_TAG_ALIASES: &[(&str, &str)] = &[ ("BIG_INT", "BIGINT"), ("SMALL_INT", "SMALLINT"), ("TEXT_WITH_LENGTH", "TEXT"), ("TINY_INT", "TINYINT"), ("VAR_CHAR", "VARCHAR"), ]; -/// The function call depth limit SQLBuild passes to every Polyglot parse. -pub(crate) const MAX_FUNCTION_CALL_DEPTH: usize = 512; /// Python's `str(0)`, the only integer text Python treats as false. pub(crate) const ZERO_TEXT: &str = "0"; /// Snowflake time and timestamp types, word separators removed, whose precision metadata omits. diff --git a/crates/sqlbuild-analysis/src/type_system/errors.rs b/crates/sqlbuild-analysis/src/type_system/errors.rs index 05145a1c60..d8aae3d875 100644 --- a/crates/sqlbuild-analysis/src/type_system/errors.rs +++ b/crates/sqlbuild-analysis/src/type_system/errors.rs @@ -1,12 +1,10 @@ //! Why type normalization fails. -/// Why Python's `normalize_type` raises instead of returning a type. +/// Why `normalize_type` raises instead of returning a type. #[derive(Debug, Clone, PartialEq, Eq)] pub enum TypeNormalizationError { - /// Polyglot does not know the dialect name: the wheel's `ValueError`. + /// SQLBuild has no type rules for the dialect name: Python's `ValueError`. UnknownDialect(String), - /// Polyglot cannot write the parsed type back as SQL: the wheel's `PolyglotError`. - Generation(String), } impl TypeNormalizationError { @@ -15,7 +13,6 @@ impl TypeNormalizationError { pub fn message(&self) -> String { match self { Self::UnknownDialect(name) => format!("Unknown dialect: {name}"), - Self::Generation(message) => message.clone(), } } } diff --git a/crates/sqlbuild-analysis/src/type_system/main/mod.rs b/crates/sqlbuild-analysis/src/type_system/main/mod.rs index 7434eded53..23df8a93ee 100644 --- a/crates/sqlbuild-analysis/src/type_system/main/mod.rs +++ b/crates/sqlbuild-analysis/src/type_system/main/mod.rs @@ -1,3 +1,3 @@ pub mod normalize_type; -pub mod polyglot_type_args; pub mod same_type; +pub mod type_args; diff --git a/crates/sqlbuild-analysis/src/type_system/main/normalize_type.rs b/crates/sqlbuild-analysis/src/type_system/main/normalize_type.rs index ef9005288c..cf32a9cd8f 100644 --- a/crates/sqlbuild-analysis/src/type_system/main/normalize_type.rs +++ b/crates/sqlbuild-analysis/src/type_system/main/normalize_type.rs @@ -1,36 +1,34 @@ -//! Python's `normalize_type`: Polyglot's parsed type first, then the text fallback. - -use polyglot_sql::{Dialect, DialectType}; +//! Python's `normalize_type`: the parsed type first, then the text fallback. use crate::type_system::_helpers::dialects::coerce_type_dialect; use crate::type_system::_helpers::fallback::normalize_with_fallback; -use crate::type_system::_helpers::parsed::{normalize_parsed, parse_type}; +use crate::type_system::_helpers::parsed::{nesting_bound, normalize_parsed, parse_type}; +use crate::type_system::_helpers::spelling::SpellingDialect; +use crate::type_system::constants::{DEEP_TYPE_STACK_BYTES, SHALLOW_TYPE_NESTING}; use crate::type_system::errors::TypeNormalizationError; use crate::type_system::models::{TypeDialect, TypeNormalization}; -/// Normalize one type as Python does for the dialect name it hands Polyglot. +/// Normalize one type in the named dialect. pub fn normalize_type( type_sql: &str, dialect: &str, ) -> Result { - let name: &str = if dialect.is_empty() { - "generic" - } else { - dialect - }; - let polyglot: Dialect = name - .parse::() - .map(Dialect::get) - .map_err(|_| TypeNormalizationError::UnknownDialect(name.to_owned()))?; + let spelling: SpellingDialect = SpellingDialect::named(dialect) + .ok_or_else(|| TypeNormalizationError::UnknownDialect(dialect.to_owned()))?; let type_dialect: Option = coerce_type_dialect(dialect); - match parse_type(type_sql, &polyglot) { - Ok(parsed) => Ok(TypeNormalization { - normalized: normalize_parsed(parsed, &polyglot, type_dialect)?, + let normalize = || match parse_type(type_sql, spelling) { + Ok(tree) => TypeNormalization { + normalized: normalize_parsed(&tree, spelling, type_dialect), parse_error: None, - }), - Err(error) => Ok(TypeNormalization { + }, + Err(error) => TypeNormalization { normalized: normalize_with_fallback(type_sql, type_dialect), parse_error: Some(error), - }), - } + }, + }; + Ok(if nesting_bound(type_sql) > SHALLOW_TYPE_NESTING { + stacker::grow(DEEP_TYPE_STACK_BYTES, normalize) + } else { + normalize() + }) } diff --git a/crates/sqlbuild-analysis/src/type_system/main/polyglot_type_args.rs b/crates/sqlbuild-analysis/src/type_system/main/polyglot_type_args.rs deleted file mode 100644 index 972ac1f6c0..0000000000 --- a/crates/sqlbuild-analysis/src/type_system/main/polyglot_type_args.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! The scalar arguments of a type as Polyglot parses it. - -use polyglot_sql::{DataType, Dialect, DialectType}; -use serde_json::{Map, Value}; - -use crate::type_system::_helpers::parsed::parse_type; -use crate::type_system::_helpers::shallow_args::shallow_args; - -/// `parse_data_type(type_sql, dialect=...).args` scalars, or the parse error text. -pub fn polyglot_type_args(type_sql: &str, dialect: &str) -> Result, String> { - let polyglot: Dialect = dialect - .parse::() - .map(Dialect::get) - .map_err(|_| format!("unknown dialect '{dialect}'"))?; - let parsed: DataType = parse_type(type_sql, &polyglot)?; - Ok(shallow_args(&parsed)) -} diff --git a/crates/sqlbuild-analysis/src/type_system/main/type_args.rs b/crates/sqlbuild-analysis/src/type_system/main/type_args.rs new file mode 100644 index 0000000000..6d92cf07cc --- /dev/null +++ b/crates/sqlbuild-analysis/src/type_system/main/type_args.rs @@ -0,0 +1,13 @@ +//! The scalar arguments of a parsed type. + +use serde_json::{Map, Value}; + +use crate::type_system::_helpers::parsed::{parse_type, type_args as tree_args}; +use crate::type_system::_helpers::spelling::SpellingDialect; + +/// The tag, name, length, precision and scale of `type_sql` in `dialect`, or the parse error. +pub fn type_args(type_sql: &str, dialect: &str) -> Result, String> { + let spelling: SpellingDialect = + SpellingDialect::named(dialect).ok_or_else(|| format!("unknown dialect '{dialect}'"))?; + parse_type(type_sql, spelling).map(|tree| tree_args(&tree)) +} diff --git a/crates/sqlbuild-analysis/src/type_system/models.rs b/crates/sqlbuild-analysis/src/type_system/models.rs index 9497d5bf9f..28323983d0 100644 --- a/crates/sqlbuild-analysis/src/type_system/models.rs +++ b/crates/sqlbuild-analysis/src/type_system/models.rs @@ -101,7 +101,7 @@ pub struct NormalizedType { pub length: Option, } -/// A normalized type and the Polyglot parse error Python logs before its text fallback. +/// A normalized type and the parse error logged before its text fallback. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TypeNormalization { pub normalized: NormalizedType, diff --git a/crates/sqlbuild-analysis/src/type_system/tests/alias_groups.json b/crates/sqlbuild-analysis/src/type_system/tests/alias_groups.json new file mode 100644 index 0000000000..ba75108724 --- /dev/null +++ b/crates/sqlbuild-analysis/src/type_system/tests/alias_groups.json @@ -0,0 +1,298 @@ +{ + "bigquery": [ + ["ARRAY< FLOAT64(10,2) >", "FLOAT64(10,2) []"], + ["ARRAY< TIMESTAMP_NTZ(0)>", "TIMESTAMPNTZ(0) []"], + ["ARRAY", "DECIMAL(10,2)[]"], + ["ARRAY", "ARRAY", "INT[]", "SMALLINT(10) []"], + ["ARRAY", "ARRAY"], + ["BIGDECIMAL( 38 , 0 )", "DECIMAL( 38 , 0 )", "number( 38 , 0 )", "NUMERIC( 38 , 0 )"], + ["BIGDECIMAL(10,2)", "DECIMAL(10, 2)", "NUMBER(10,2)", "NUMERIC(10,2)"], + ["BIGINT", "BIGINT(10)", "INT", "INT(10)", "INT64", "INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)", "integer", "integer(10)", "LONG", "LONG(10)", "SMALLINT", "SMALLINT(10)"], + ["BLOB", "bytes"], + ["BOOL", "BOOLEAN"], + ["CHAR", "CHARACTER"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DOUBLE", "DOUBLE PRECISION", "FLOAT", "FLOAT64", "FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)", "REAL"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)", "FLOAT(10)", "REAL(10)"], + ["GEOGRAPHY", "GEOGRAPHY(10)"], + ["STRUCT(a INT)", "STRUCT(a INTEGER(10) )"], + ["TIMESTAMP", "TIMESTAMP(0)", "timestamp(10)"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "timestamptz(0)", "TIMESTAMPTZ(10)", "TIMESTAMP_TZ", "TIMESTAMP_TZ(0)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)"], + ["bigdecimal", "decimal", "NUMBER", "NUMERIC"], + ["bigdecimal(10)", "decimal(10)", "NUMBER(10)", "NUMERIC(10)"], + ["bool(10)", "BOOLEAN(10)"], + ["character varying", "STRING", "TEXT", "VARCHAR", "VARCHAR(MAX)"], + ["date", "DATE(10)"] + ], + "clickhouse": [ + ["BIGINT", "BIGINT(10)"], + ["BLOB", "Nullable(String)"], + ["Bool", "BOOL(10)", "BOOLEAN"], + ["CHAR", "CHAR(10)", "CHARACTER", "CHARACTER VARYING", "CHARACTER VARYING(10)", "CHARACTER(10)", "String", "STRING(10)", "TEXT", "VARCHAR", "VARCHAR(10)"], + ["DECIMAL", "NUMERIC"], + ["DECIMAL(10)", "NUMERIC(10)"], + ["DECIMAL(10,2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)"], + ["Date", "DATE(10)"], + ["DateTime", "DateTime('UTC')", "DATETIME(10)"], + ["FLOAT", "REAL"], + ["FLOAT(10)", "REAL(10)"], + ["Float64", "FLOAT64(10)", "FLOAT64(10,2)"], + ["INT", "INT(10)", "INTEGER", "INTEGER(10)"], + ["Int64", "INT64(10)", "INT64(10,2)"], + ["LONG", "LONG(10)"], + ["SMALLINT", "SMALLINT(10)"], + ["TIMESTAMP", "TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMP(10)", "TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(10)"], + ["TINYINT", "TINYINT(10)"] + ], + "databricks": [ + ["ARRAY< FLOAT64(10,2) >", "FLOAT64(10,2) []"], + ["ARRAY< TIMESTAMP_NTZ(0)>", "TIMESTAMPNTZ(0) []"], + ["ARRAY", "DECIMAL(10,2)[]"], + ["ARRAY", "INT[]"], + ["ARRAY", "ARRAY"], + ["BIGINT", "INT64"], + ["BOOL", "BOOLEAN", "BOOLEAN(10)"], + ["CHAR", "CHARACTER", "character varying", "STRING", "TEXT", "VARCHAR"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DECIMAL( 38 , 0 )", "NUMERIC( 38 , 0 )"], + ["DECIMAL(10, 2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION", "FLOAT64"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)"], + ["FLOAT", "FLOAT(10)", "REAL", "REAL(10)"], + ["FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)"], + ["INT", "integer"], + ["INT(10)", "integer(10)"], + ["INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)"], + ["LONG", "LONG(10)"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)"], + ["binary", "BLOB"], + ["date", "DATE(10)"], + ["decimal", "NUMERIC"], + ["decimal(10)", "NUMERIC(10)"], + ["time", "TIME(0)", "TIME(10)"], + ["timestamptz(0)", "TIMESTAMP_TZ(0)"] + ], + "duckdb": [ + ["ARRAY< FLOAT64(10,2) >", "FLOAT64(10,2) []"], + ["ARRAY< TIMESTAMP_NTZ(0)>", "TIMESTAMPNTZ(0) []"], + ["ARRAY", "DECIMAL(10,2)[]"], + ["ARRAY", "INTEGER[]", "INT[]"], + ["ARRAY", "ARRAY"], + ["BIGINT", "INT64"], + ["BOOL", "BOOLEAN", "BOOLEAN(10)"], + ["CHAR", "CHARACTER", "character varying", "STRING", "TEXT", "VARCHAR"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DECIMAL( 38 , 0 )", "NUMERIC( 38 , 0 )"], + ["DECIMAL(10, 2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION", "FLOAT64"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)"], + ["FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)"], + ["INT", "integer"], + ["INT(10)", "integer(10)"], + ["INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)"], + ["LONG", "LONG(10)"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "timestamptz(0)", "TIMESTAMPTZ(10)", "TIMESTAMP_TZ", "TIMESTAMP_TZ(0)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)"], + ["date", "DATE(10)"], + ["decimal", "NUMERIC"], + ["decimal(10)", "NUMERIC(10)"] + ], + "generic": [ + ["ARRAY< FLOAT64(10,2) >", "FLOAT64(10,2) []"], + ["ARRAY< TIMESTAMP_NTZ(0)>", "TIMESTAMPNTZ(0) []"], + ["ARRAY", "DECIMAL(10,2)[]"], + ["ARRAY", "INT[]"], + ["ARRAY", "ARRAY"], + ["BIGINT", "INT64"], + ["BOOL", "BOOLEAN", "BOOLEAN(10)"], + ["CHAR", "CHARACTER"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DECIMAL( 38 , 0 )", "NUMERIC( 38 , 0 )"], + ["DECIMAL(10, 2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION", "FLOAT64"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)"], + ["FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)"], + ["INT", "integer"], + ["INT(10)", "integer(10)"], + ["INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)"], + ["LONG", "LONG(10)"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)"], + ["character varying", "VARCHAR", "VARCHAR(MAX)"], + ["date", "DATE(10)"], + ["decimal", "NUMERIC"], + ["decimal(10)", "NUMERIC(10)"], + ["timestamptz(0)", "TIMESTAMP_TZ(0)"] + ], + "mssql": [ + ["BIGINT", "INT64"], + ["BOOL", "BOOLEAN"], + ["CHAR", "CHARACTER"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING", "VARCHAR"], + ["CHARACTER VARYING(10)", "STRING(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATE", "DATE(10)"], + ["DATETIME", "DATETIME(10)"], + ["DECIMAL", "NUMERIC"], + ["DECIMAL(10)", "NUMERIC(10)"], + ["DECIMAL(10,2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION", "FLOAT"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)", "FLOAT(10)"], + ["FLOAT64(10)", "FLOAT64(10,2)"], + ["INT", "INTEGER"], + ["INT(10)", "INTEGER(10)"], + ["INT64(10)", "INT64(10,2)"], + ["LONG", "LONG(10)"], + ["STRING", "TEXT"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(10)"] + ], + "postgres": [ + ["ARRAY< FLOAT64(10,2) >", "FLOAT64(10,2) []"], + ["ARRAY< TIMESTAMP_NTZ(0)>", "TIMESTAMPNTZ(0) []"], + ["ARRAY", "DECIMAL(10,2)[]"], + ["ARRAY", "integer[]", "INT[]"], + ["ARRAY", "ARRAY"], + ["BIGINT", "INT64"], + ["BLOB", "bytea"], + ["BOOL", "BOOLEAN", "BOOLEAN(10)"], + ["CHAR", "CHARACTER"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DECIMAL( 38 , 0 )", "NUMERIC( 38 , 0 )"], + ["DECIMAL(10, 2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)"], + ["FLOAT(10)", "REAL(10)"], + ["FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)"], + ["INT", "integer"], + ["INT(10)", "integer(10)"], + ["INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)"], + ["LONG", "LONG(10)"], + ["SMALLINT", "TINYINT"], + ["SMALLINT(10)", "TINYINT(10)"], + ["STRING", "TEXT"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)"], + ["character varying", "VARCHAR", "VARCHAR(MAX)"], + ["date", "DATE(10)"], + ["decimal", "NUMERIC"], + ["decimal(10)", "NUMERIC(10)"], + ["time", "time without time zone"], + ["timestamptz(0)", "TIMESTAMP_TZ(0)"] + ], + "snowflake": [ + ["ARRAY< FLOAT64(10,2) >", "FLOAT64(10,2) []"], + ["ARRAY< TIMESTAMP_NTZ(0)>", "TIMESTAMPNTZ(0) []"], + ["ARRAY", "DECIMAL(10,2)[]"], + ["ARRAY", "INT[]"], + ["ARRAY", "ARRAY"], + ["BIGDECIMAL(10,2)", "bignumeric(10, 2)", "DECIMAL(10, 2)", "NUMBER(10,2)", "NUMERIC(10,2)"], + ["BOOL", "BOOLEAN", "BOOLEAN(10)"], + ["CHAR", "CHARACTER"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DOUBLE", "DOUBLE PRECISION"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)"], + ["FLOAT", "REAL"], + ["FLOAT(10)", "REAL(10)"], + ["FLOAT64", "FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)"], + ["GEOGRAPHY", "GEOGRAPHY(10)"], + ["JSON", "variant"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)", "TIMESTAMP_NTZ(9)"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)", "TIMESTAMP_LTZ(9)"], + ["bigdecimal", "BIGDECIMAL( 38 , 0 )", "BIGINT", "BIGINT(10)", "BIGNUMERIC", "BIGNUMERIC( 38 , 0 )", "decimal", "DECIMAL( 38 , 0 )", "INT", "INT(10)", "INT64", "INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)", "integer", "integer(10)", "LONG", "LONG(10)", "NUMBER", "number( 38 , 0 )", "NUMERIC", "NUMERIC( 38 , 0 )", "SMALLINT", "SMALLINT(10)", "TINYINT", "TINYINT(10)"], + ["bigdecimal(10)", "BIGNUMERIC(10)", "decimal(10)", "NUMBER(10)", "NUMERIC(10)"], + ["character varying", "STRING", "TEXT", "VARCHAR", "VARCHAR(16777216)", "VARCHAR(MAX)"], + ["date", "DATE(10)"], + ["timestamptz(0)", "TIMESTAMP_TZ(0)"] + ], + "sqlite": [ + ["BIGINT", "INT64"], + ["BOOL", "BOOLEAN", "BOOLEAN(10)"], + ["CHAR", "CHARACTER", "CHARACTER VARYING", "TEXT", "VARCHAR"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "STRING(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATE", "DATE(10)"], + ["DATETIME", "DATETIME(10)"], + ["DECIMAL", "NUMERIC"], + ["DECIMAL(10)", "NUMERIC(10)"], + ["DECIMAL(10,2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION", "REAL"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)", "REAL(10)"], + ["FLOAT64(10)", "FLOAT64(10,2)"], + ["INT", "INTEGER", "SMALLINT", "SMALLINT(10)"], + ["INT(10)", "INTEGER(10)"], + ["INT64(10)", "INT64(10,2)"], + ["LONG", "LONG(10)"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(10)"] + ], + "tsql": [ + ["ARRAY< DEC( 38 , 0 ) >", "ARRAY< FLOAT64(10,2) >", "ARRAY< TIMESTAMP(10) >", "ARRAY< TIMESTAMP_LTZ>", "ARRAY< TIMESTAMP_NTZ(0)>", "ARRAY>", "ARRAY", "ARRAY", "ARRAY", "ARRAY", "ARRAY", "JSON", "nvarchar(max)"], + ["BIGINT", "INT64"], + ["BOOL", "BOOLEAN"], + ["CHAR", "CHARACTER"], + ["CHAR(10)", "CHARACTER(10)"], + ["CHARACTER VARYING(10)", "string(10)", "TEXT(10)", "VARCHAR(10)"], + ["DATETIME", "DATETIME(0)", "DATETIME(10)"], + ["DECIMAL( 38 , 0 )", "NUMERIC( 38 , 0 )"], + ["DECIMAL(10, 2)", "NUMERIC(10,2)"], + ["DOUBLE", "DOUBLE PRECISION", "FLOAT"], + ["DOUBLE PRECISION(10)", "DOUBLE(10)", "FLOAT(10)"], + ["FLOAT64( 38 , 0 )", "FLOAT64(10)", "FLOAT64(10, 2)", "FLOAT64(MAX)"], + ["INT", "integer"], + ["INT(10)", "integer(10)"], + ["INT64( 38 , 0 )", "INT64(10)", "INT64(10,2)", "INT64(MAX)"], + ["LONG", "LONG(10)"], + ["TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"], + ["TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", "TIMESTAMP_TZ"], + ["TIMESTAMPNTZ", "TIMESTAMPNTZ(0)", "TIMESTAMPNTZ(10)", "TIMESTAMP_NTZ", "TIMESTAMP_NTZ(0)", "TIMESTAMP_NTZ(10)"], + ["TIMESTAMPTZ(10)", "TIMESTAMP_TZ(10)"], + ["TIMESTAMP_LTZ", "TIMESTAMP_LTZ(0)", "TIMESTAMP_LTZ(10)"], + ["character varying", "VARCHAR", "VARCHAR(MAX)"], + ["date", "DATE(10)"], + ["decimal", "NUMERIC"], + ["decimal(10)", "NUMERIC(10)"], + ["ntext", "STRING", "TEXT"], + ["timestamptz(0)", "TIMESTAMP_TZ(0)"], + ["uniqueidentifier", "UUID"] + ] +} diff --git a/crates/sqlbuild-analysis/src/type_system/tests/helpers.rs b/crates/sqlbuild-analysis/src/type_system/tests/helpers.rs index cc51b6f245..cb6ab068fa 100644 --- a/crates/sqlbuild-analysis/src/type_system/tests/helpers.rs +++ b/crates/sqlbuild-analysis/src/type_system/tests/helpers.rs @@ -1,4 +1,5 @@ -use crate::type_system::models::{PythonInteger, TypeNormalization}; +use crate::type_system::main::normalize_type::normalize_type; +use crate::type_system::models::{NormalizedType, PythonInteger, TypeNormalization}; pub(super) fn normalization_text(normalization: &TypeNormalization) -> String { let optional = @@ -14,3 +15,10 @@ pub(super) fn normalization_text(normalization: &TypeNormalization) -> String { normalization.parse_error.as_deref().unwrap_or("-"), ) } + +/// The normalized type of `type_sql` in `dialect`, which must be a known dialect. +pub(super) fn normalized_type(type_sql: &str, dialect: &str) -> NormalizedType { + normalize_type(type_sql, dialect) + .expect("the dialect is known") + .normalized +} diff --git a/crates/sqlbuild-analysis/src/type_system/tests/test_normalize_type.rs b/crates/sqlbuild-analysis/src/type_system/tests/test_normalize_type.rs index afee3fc479..3e1f843a2a 100644 --- a/crates/sqlbuild-analysis/src/type_system/tests/test_normalize_type.rs +++ b/crates/sqlbuild-analysis/src/type_system/tests/test_normalize_type.rs @@ -83,20 +83,100 @@ fn given_type_strings_when_normalizing_then_python_normalization_is_returned() { type_sql: "order status", dialect: "generic", expected_normalization: Ok( - "ORDERSTATUS | other | - | - | - | Parse error at line 1, column 13: Unexpected token after data type: status", + "ORDERSTATUS | other | - | - | - | unexpected 'status' after the type", ), }, NormalizeTypeTestCase { - description: "every Polyglot dialect is normalized natively", + description: "MotherDuck reads and spells types as DuckDB", + type_sql: "STRUCT(a INT, b VARCHAR(10))[]", + dialect: "motherduck", + expected_normalization: Ok("STRUCT(AINT,BTEXT(10))[] | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "a ClickHouse nested type argument, as cursor types arrive upper-cased", + type_sql: "NULLABLE(DATETIME64(3))", + dialect: "clickhouse", + expected_normalization: Ok("NULLABLE(DATETIME64(3)) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "a ClickHouse enum member and its value", + type_sql: "ENUM8('a' = 1, 'it''s' = -2)", + dialect: "clickhouse", + expected_normalization: Ok("ENUM8('A'=1,'IT''S'=-2) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "a ClickHouse named tuple field", + type_sql: "TUPLE(a INT64, b LOWCARDINALITY(NULLABLE(STRING)))", + dialect: "clickhouse", + expected_normalization: Ok( + "TUPLE(AINT64,BLOWCARDINALITY(NULLABLE(STRING))) | other | - | - | - | -", + ), + }, + NormalizeTypeTestCase { + description: "ClickHouse respells an alias inside Nullable as the bare alias", + type_sql: "NULLABLE(INT)", + dialect: "clickhouse", + expected_normalization: Ok("NULLABLE(INT32) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "ClickHouse respells a Nullable alias inside an array", + type_sql: "ARRAY(NULLABLE(INT))", + dialect: "clickhouse", + expected_normalization: Ok("ARRAY(NULLABLE(INT32)) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "ClickHouse respells a Nullable timestamp", + type_sql: "NULLABLE(TIMESTAMP)", + dialect: "clickhouse", + expected_normalization: Ok("NULLABLE(DATETIME) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "ClickHouse respells a Nullable boolean", + type_sql: "NULLABLE(BOOLEAN)", + dialect: "clickhouse", + expected_normalization: Ok("NULLABLE(BOOL) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "ClickHouse reads a sized BINARY as FixedString", + type_sql: "BINARY(5)", + dialect: "clickhouse", + expected_normalization: Ok("FIXEDSTRING(5) | other | - | - | - | -"), + }, + NormalizeTypeTestCase { + description: "ClickHouse refuses a BINARY without a length", + type_sql: "BINARY", + dialect: "clickhouse", + expected_normalization: Ok( + "BINARY | other | - | - | - | ClickHouse's BINARY needs a length", + ), + }, + NormalizeTypeTestCase { + description: "ClickHouse refuses a Nullable inside a Nullable", + type_sql: "NULLABLE(NULLABLE(INT))", + dialect: "clickhouse", + expected_normalization: Ok( + "NULLABLE(NULLABLE(INT)) | other | - | - | - | Nullable cannot wrap the nullable type", + ), + }, + NormalizeTypeTestCase { + description: "ClickHouse reads a bare alias with its Polyglot nullable spelling", type_sql: "INT", - dialect: "mysql", - expected_normalization: Ok("INT | integer | - | - | - | -"), + dialect: "clickhouse", + expected_normalization: Ok("NULLABLE(INT32) | integer | - | - | - | -"), }, NormalizeTypeTestCase { - description: "a dialect Polyglot does not know raises Python's error", + description: "an enum member outside ClickHouse falls back to the text", + type_sql: "ENUM8('a' = 1)", + dialect: "tsql", + expected_normalization: Ok( + "ENUM8('A'=1) | other | - | - | - | unexpected character '''", + ), + }, + NormalizeTypeTestCase { + description: "a dialect no SQLBuild adapter ships raises Python's error", type_sql: "INT", - dialect: "motherduck", - expected_normalization: Err("Unknown dialect: motherduck"), + dialect: "mysql", + expected_normalization: Err("Unknown dialect: mysql"), }, NormalizeTypeTestCase { description: "non-ASCII text upper-cases with Python's Unicode mapping", @@ -110,7 +190,8 @@ fn given_type_strings_when_normalizing_then_python_normalization_is_returned() { dialect: "duckdb", expected_normalization: Ok( "DECIMAL(99999999999999999999,2) | decimal | 99999999999999999999 | 2 | - | \ - Parse error at line 1, column 30: Invalid number: 99999999999999999999", + Parse(Parse { message: \"Invalid number: 99999999999999999999\", line: 1, \ + column: 50, start: 48, end: 49 })", ), }, NormalizeTypeTestCase { @@ -118,7 +199,8 @@ fn given_type_strings_when_normalizing_then_python_normalization_is_returned() { type_sql: "VARCHAR(\u{663})", dialect: "snowflake", expected_normalization: Ok( - "VARCHAR(3) | string | - | - | 3 | Parse error at line 1, column 10: Expected number", + "VARCHAR(3) | string | - | - | 3 | Parse(Parse { message: \"Expected number\", \ + line: 1, column: 30, start: 28, end: 29 })", ), }, ]; diff --git a/crates/sqlbuild-analysis/src/type_system/tests/test_same_type.rs b/crates/sqlbuild-analysis/src/type_system/tests/test_same_type.rs index c97bf25c73..52f84e5e99 100644 --- a/crates/sqlbuild-analysis/src/type_system/tests/test_same_type.rs +++ b/crates/sqlbuild-analysis/src/type_system/tests/test_same_type.rs @@ -1,9 +1,15 @@ +use std::collections::BTreeMap; + use crate::type_system::main::normalize_type::normalize_type; use crate::type_system::main::same_type::same_type; -use crate::type_system::tests::test_types::SameTypeTestCase; +use crate::type_system::tests::helpers::normalized_type; +use crate::type_system::tests::test_types::{AliasGroupsTestCase, SameTypeTestCase}; + +/// Each dialect's groups of valid spellings that the Polyglot-based normalization read alike. +const ALIAS_GROUPS: &str = include_str!("alias_groups.json"); #[test] -fn given_two_types_when_comparing_then_an_unstated_snowflake_temporal_precision_matches() { +fn given_two_types_when_comparing_then_same_type_answers() { let test_cases = [ SameTypeTestCase { description: "a declared millisecond time matches metadata's precision-less time", @@ -65,6 +71,24 @@ fn given_two_types_when_comparing_then_an_unstated_snowflake_temporal_precision_ types: ("VARCHAR(10)", "VARCHAR(20)"), expected_same: false, }, + SameTypeTestCase { + description: "Postgres FLOAT without a precision is DOUBLE PRECISION, not REAL", + dialect: "postgres", + types: ("FLOAT", "REAL"), + expected_same: false, + }, + SameTypeTestCase { + description: "BigQuery's TIMESTAMP is an absolute time, not a civil DATETIME", + dialect: "bigquery", + types: ("TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE"), + expected_same: false, + }, + SameTypeTestCase { + description: "BigQuery's civil time is DATETIME", + dialect: "bigquery", + types: ("DATETIME", "TIMESTAMP WITHOUT TIME ZONE"), + expected_same: true, + }, ]; for test_case in test_cases { let (left, right) = test_case.types; @@ -82,3 +106,34 @@ fn given_two_types_when_comparing_then_an_unstated_snowflake_temporal_precision_ ); } } + +#[test] +fn given_spellings_of_one_type_when_comparing_then_every_pair_is_the_same_type() { + let test_cases = [AliasGroupsTestCase { + description: "every alias group of the Polyglot-based normalization", + groups: ALIAS_GROUPS, + expected_same: true, + }]; + for test_case in test_cases { + let groups: BTreeMap>> = + serde_json::from_str(test_case.groups).expect("the alias groups are JSON"); + for (dialect, dialect_groups) in &groups { + for group in dialect_groups { + for (index, left) in group.iter().enumerate() { + for right in &group[index + 1..] { + assert_eq!( + same_type( + &normalized_type(left, dialect), + &normalized_type(right, dialect), + dialect, + ), + test_case.expected_same, + "{}: {dialect} `{left}` and `{right}`", + test_case.description + ); + } + } + } + } + } +} diff --git a/crates/sqlbuild-analysis/src/type_system/tests/test_types.rs b/crates/sqlbuild-analysis/src/type_system/tests/test_types.rs index 2b9aeec95d..b678a56f7f 100644 --- a/crates/sqlbuild-analysis/src/type_system/tests/test_types.rs +++ b/crates/sqlbuild-analysis/src/type_system/tests/test_types.rs @@ -30,3 +30,10 @@ pub(super) struct SameTypeTestCase { pub(super) types: (&'static str, &'static str), pub(super) expected_same: bool, } + +pub(super) struct AliasGroupsTestCase { + pub(super) description: &'static str, + /// JSON: each dialect's groups of spellings of one type. + pub(super) groups: &'static str, + pub(super) expected_same: bool, +} diff --git a/crates/sqlbuild-analysis/src/warehouse_types/_helpers/mod.rs b/crates/sqlbuild-analysis/src/warehouse_types/_helpers/mod.rs new file mode 100644 index 0000000000..b0d2d9639d --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/_helpers/mod.rs @@ -0,0 +1 @@ +pub(crate) mod type_trees; diff --git a/crates/sqlbuild-analysis/src/warehouse_types/_helpers/type_trees.rs b/crates/sqlbuild-analysis/src/warehouse_types/_helpers/type_trees.rs new file mode 100644 index 0000000000..6a810dcfc0 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/_helpers/type_trees.rs @@ -0,0 +1,67 @@ +//! Type trees from SQLQuack's parsers: Postgres's type grammar, or a cast target's type. + +use serde_json::Value; +use sqlquack_syntax::tokens::models::Dialect; +use sqlquack_syntax::{Expression, ParseOptions, parse_strict}; + +use crate::warehouse_types::constants::{CAST_KEY, CAST_TARGET_KEY}; + +/// `spelling` read by `sqlquack-syntax-postgres`'s type grammar. +pub(crate) fn postgres_type(spelling: &str) -> Result { + let data_type = + sqlquack_syntax_postgres::parse_type(spelling).map_err(|error| format!("{error:?}"))?; + serde_json::to_value(data_type).map_err(|error| error.to_string()) +} + +/// `spelling` read as a cast target by SQLQuack's core parser for `dialect`. +pub(crate) fn core_type(spelling: &str, dialect: Dialect) -> Result { + let statements: Vec = + parse_strict(&cast(spelling), dialect, &ParseOptions::default()) + .map_err(|error| format!("{error:?}"))?; + single_cast_target(spelling, &statements) +} + +/// `spelling` read as a cast target by `sqlquack-syntax-bigquery`. +pub(crate) fn bigquery_type(spelling: &str) -> Result { + let statements: Vec = sqlquack_syntax_bigquery::parse_strict(&cast(spelling)) + .map_err(|error| format!("{error:?}"))? + .statements; + single_cast_target(spelling, &statements) +} + +/// A statement that casts NULL to `spelling`. +fn cast(spelling: &str) -> String { + format!("SELECT CAST(NULL AS {spelling})") +} + +/// The target type of the one cast in the one statement, or why `spelling` is not one type. +fn single_cast_target(spelling: &str, statements: &[Expression]) -> Result { + let tree: Value = serde_json::to_value(statements).map_err(|error| error.to_string())?; + match (statements.len(), cast_targets(&tree).as_slice()) { + (1, [target]) => Ok((*target).clone()), + _ => Err(format!("`{spelling}` is not a single type name")), + } +} + +/// Every cast's target type in `tree`, in order. +fn cast_targets(tree: &Value) -> Vec<&Value> { + let mut targets: Vec<&Value> = Vec::new(); + let mut pending: Vec<&Value> = vec![tree]; + while let Some(node) = pending.pop() { + match node { + Value::Object(fields) => { + for (key, value) in fields.iter().rev() { + pending.push(value); + if key == CAST_KEY + && let Some(target) = value.get(CAST_TARGET_KEY) + { + targets.push(target); + } + } + } + Value::Array(items) => pending.extend(items.iter().rev()), + _ => {} + } + } + targets +} diff --git a/crates/sqlbuild-analysis/src/warehouse_types/constants.rs b/crates/sqlbuild-analysis/src/warehouse_types/constants.rs new file mode 100644 index 0000000000..3a91139238 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/constants.rs @@ -0,0 +1,12 @@ +//! Constants of warehouse type parsing. + +/// The tree key of a cast. +pub(crate) const CAST_KEY: &str = "cast"; +/// The tree key of a cast's target type. +pub(crate) const CAST_TARGET_KEY: &str = "to"; +/// The dialect whose type names go through PostgreSQL's own type grammar. +pub(crate) const POSTGRES_DIALECT: &str = "postgres"; +/// The dialect whose type names are parsed as a Snowflake cast's target. +pub(crate) const SNOWFLAKE_DIALECT: &str = "snowflake"; +/// The dialect whose type names are parsed as a BigQuery cast's target. +pub(crate) const BIGQUERY_DIALECT: &str = "bigquery"; diff --git a/crates/sqlbuild-analysis/src/warehouse_types/main/mod.rs b/crates/sqlbuild-analysis/src/warehouse_types/main/mod.rs new file mode 100644 index 0000000000..5d33207517 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/main/mod.rs @@ -0,0 +1,2 @@ +pub mod parse_warehouse_type; +pub mod type_tree; diff --git a/crates/sqlbuild-analysis/src/warehouse_types/main/parse_warehouse_type.rs b/crates/sqlbuild-analysis/src/warehouse_types/main/parse_warehouse_type.rs new file mode 100644 index 0000000000..4128082fb4 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/main/parse_warehouse_type.rs @@ -0,0 +1,18 @@ +//! `parse_warehouse_type`: a warehouse type name as SQLQuack's parser for its dialect reads it. + +use serde_json::Value; +use sqlquack_syntax::tokens::models::Dialect; + +use crate::warehouse_types::constants::{BIGQUERY_DIALECT, POSTGRES_DIALECT, SNOWFLAKE_DIALECT}; +use crate::warehouse_types::main::type_tree::type_tree; +use crate::warehouse_types::models::TypeParser; + +/// The type tree of `spelling` in `dialect`, None without a parser, or why the parser rejects it. +pub fn parse_warehouse_type(spelling: &str, dialect: &str) -> Result, String> { + match dialect { + POSTGRES_DIALECT => type_tree(spelling, TypeParser::Postgres).map(Some), + SNOWFLAKE_DIALECT => type_tree(spelling, TypeParser::Core(Dialect::Snowflake)).map(Some), + BIGQUERY_DIALECT => type_tree(spelling, TypeParser::BigQuery).map(Some), + _ => Ok(None), + } +} diff --git a/crates/sqlbuild-analysis/src/warehouse_types/main/type_tree.rs b/crates/sqlbuild-analysis/src/warehouse_types/main/type_tree.rs new file mode 100644 index 0000000000..cfa9c12309 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/main/type_tree.rs @@ -0,0 +1,15 @@ +//! `type_tree`: a type name as one of SQLQuack's parsers reads it. + +use serde_json::Value; + +use crate::warehouse_types::_helpers::type_trees::{bigquery_type, core_type, postgres_type}; +use crate::warehouse_types::models::TypeParser; + +/// The type tree `parser` reads from `spelling`, or why it rejects it. +pub fn type_tree(spelling: &str, parser: TypeParser) -> Result { + match parser { + TypeParser::Postgres => postgres_type(spelling), + TypeParser::BigQuery => bigquery_type(spelling), + TypeParser::Core(dialect) => core_type(spelling, dialect), + } +} diff --git a/crates/sqlbuild-analysis/src/warehouse_types/mod.rs b/crates/sqlbuild-analysis/src/warehouse_types/mod.rs new file mode 100644 index 0000000000..8e1b42f6e3 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/mod.rs @@ -0,0 +1,6 @@ +//! Warehouse column type names as SQLQuack's parser for each warehouse dialect reads them. + +pub(crate) mod _helpers; +pub(crate) mod constants; +pub mod main; +pub mod models; diff --git a/crates/sqlbuild-analysis/src/warehouse_types/models.rs b/crates/sqlbuild-analysis/src/warehouse_types/models.rs new file mode 100644 index 0000000000..8fb7d377e6 --- /dev/null +++ b/crates/sqlbuild-analysis/src/warehouse_types/models.rs @@ -0,0 +1,14 @@ +//! The SQLQuack parsers that read warehouse type names. + +use sqlquack_syntax::tokens::models::Dialect; + +/// Which SQLQuack parser reads a type name. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum TypeParser { + /// `sqlquack-syntax-postgres`'s type grammar. + Postgres, + /// A cast target read by `sqlquack-syntax-bigquery`. + BigQuery, + /// A cast target read by SQLQuack's core parser for the dialect. + Core(Dialect), +} diff --git a/crates/sqlbuild-cli/Cargo.toml b/crates/sqlbuild-cli/Cargo.toml index fe634085dc..de21278294 100644 --- a/crates/sqlbuild-cli/Cargo.toml +++ b/crates/sqlbuild-cli/Cargo.toml @@ -27,6 +27,7 @@ reqwest.workspace = true rpassword.workspace = true serde.workspace = true serde_json = { workspace = true, features = ["arbitrary_precision"] } +sqlbuild-analysis.workspace = true sqlbuild-build-features.workspace = true sqlbuild-cache.workspace = true sqlbuild-config.workspace = true diff --git a/crates/sqlbuild-cli/src/adhoc/_helpers/mod.rs b/crates/sqlbuild-cli/src/adhoc/_helpers/mod.rs new file mode 100644 index 0000000000..f06700ef53 --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/_helpers/mod.rs @@ -0,0 +1 @@ +pub(crate) mod planner; diff --git a/crates/sqlbuild-cli/src/adhoc/_helpers/planner.rs b/crates/sqlbuild-cli/src/adhoc/_helpers/planner.rs new file mode 100644 index 0000000000..d999dd7501 --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/_helpers/planner.rs @@ -0,0 +1,198 @@ +//! Parse a command with the CLI's table, keep only allowed flags and resolve selectors to exact node keys. + +use std::collections::{BTreeMap, BTreeSet}; + +use serde_json::{Value as Json, json}; +use sqlbuild_analysis::graph::main::build_project_graph::build_project_graph; +use sqlbuild_analysis::graph::main::resolve_selectors::resolve_selectors; +use sqlbuild_analysis::graph::models::GraphResource; +use sqlbuild_core::text::main::python_str_repr::python_str_repr; +use sqlbuild_orchestration::runs::models::{CommandPlan, PlannerResource}; +use sqlbuild_orchestration::runs::types::CommandPlanner; + +use crate::adhoc::constants::{ + ADHOC_VERBS, ALLOWED_DESTS, BUILD_VERB, COMMAND_DEST, INTEGER_CONVERTERS, LOAD_SOURCES_DEST, + POSITIVE_INTEGER_CONVERTER, REFUSED_DESTS, SELECTOR_DESTS, +}; +use crate::argv::main::job_settings_arguments::job_settings_arguments; +use crate::argv::main::parse_cli_arguments::parse_cli_arguments; +use crate::argv::models::{ArgvSettings, Namespace, TypeFailure, Value}; +use crate::argv::types::ArgvHost; + +/// The planner `sqb server` is built with. +pub(crate) struct CliPlanner; + +impl CommandPlanner for CliPlanner { + fn plan( + &self, + command: &[String], + resources: &[PlannerResource], + ) -> Result { + let verb = command + .first() + .ok_or_else(|| "enter a command, such as build --select orders".to_owned())?; + if !ADHOC_VERBS.contains(&verb.as_str()) { + return Err(format!( + "sqb {verb} is not available as an ad-hoc run; use build, test, seed, audit or freshness" + )); + } + let parsed = parse(command)?; + let bare = parse(std::slice::from_ref(verb))?; + let mut selectors: BTreeMap<&str, Vec> = BTreeMap::new(); + let mut settings: BTreeMap = BTreeMap::new(); + for (dest, value) in &parsed.entries { + if bare.value(dest) == *value || dest == COMMAND_DEST { + continue; + } + if let Some(selector) = SELECTOR_DESTS.iter().find(|name| **name == dest) { + selectors.insert(selector, texts(value)); + continue; + } + settings.insert(dest.clone(), setting(dest, value)?); + } + if verb == BUILD_VERB { + job_settings_arguments(&settings)?; + } + let mut select: Vec = selectors.remove("select").unwrap_or_default(); + select.extend(selectors.remove("positional_select").unwrap_or_default()); + let exclude: Vec = selectors.remove("exclude").unwrap_or_default(); + Ok(CommandPlan { + verb: verb.clone(), + selection: resolve(resources, &select, &exclude)?, + settings, + }) + } +} + +/// Whole numbers convert as the parser needs them; paths and variables stay text so the refusal can name them. +struct PlannerHost; + +impl ArgvHost for PlannerHost { + fn convert(&mut self, converter: &str, text: &str) -> Result { + if !INTEGER_CONVERTERS.contains(&converter) { + return Ok(Value::Str(text.to_owned())); + } + let number: i64 = text.trim().parse().map_err(|_| TypeFailure::Invalid)?; + if converter == POSITIVE_INTEGER_CONVERTER && number < 1 { + return Err(TypeFailure::ArgumentType( + "must be a positive integer".to_owned(), + )); + } + Ok(Value::Int(number.to_string())) + } + + fn repr(&self, text: &str) -> String { + python_str_repr(text) + } + + fn program_version(&mut self) -> String { + String::new() + } +} + +fn parse(command: &[String]) -> Result { + let settings = ArgvSettings { + columns: 100, + use_color: false, + }; + let outcome = parse_cli_arguments(command, Vec::new(), settings, &mut PlannerHost); + outcome.parsed.map_err(|_| { + let written: String = outcome.writes.into_iter().map(|(_, text)| text).collect(); + written + .lines() + .rev() + .find(|line| line.contains("error")) + .map_or_else( + || "help is not a run; enter a command to check it".to_owned(), + |line| line.trim().to_owned(), + ) + }) +} + +fn texts(value: &Value) -> Vec { + match value { + Value::List(items) => items + .iter() + .filter_map(|item| match item { + Value::Str(text) => Some(text.clone()), + _ => None, + }) + .collect(), + _ => Vec::new(), + } +} + +/// One allowed flag as a frozen setting, or why the flag is refused. +fn setting(dest: &str, value: &Value) -> Result { + if let Some((_, flag, reason)) = REFUSED_DESTS.iter().find(|(name, _, _)| *name == dest) { + return Err(format!("{flag} is not available for ad-hoc runs: {reason}")); + } + if dest == LOAD_SOURCES_DEST && *value == Value::Bool(true) { + return Err( + "--load is not available for ad-hoc runs: source loads are not part of ad-hoc runs yet" + .into(), + ); + } + if !ALLOWED_DESTS.contains(&dest) { + return Err(format!( + "{} is not available for ad-hoc runs", + flag_of(dest) + )); + } + match value { + Value::Bool(flag) => Ok(json!(flag)), + Value::Int(text) => text + .parse::() + .map(|number| json!(number)) + .map_err(|_| format!("{} must be a whole number", flag_of(dest))), + Value::Str(text) => Ok(json!(text)), + _ => Err(format!( + "{} is not available for ad-hoc runs", + flag_of(dest) + )), + } +} + +/// The flag people type for a parser destination that has its own `--name` flag. +fn flag_of(dest: &str) -> String { + format!("--{}", dest.replace('_', "-")) +} + +/// Exact `kind:name` keys, as `sqb build` would select them from this publication. +fn resolve( + resources: &[PlannerResource], + select: &[String], + exclude: &[String], +) -> Result, String> { + let graph_resources: Vec = resources.iter().filter_map(graph_resource).collect(); + let graph = build_project_graph(&graph_resources); + let selected: BTreeSet = resolve_selectors(&graph, select, exclude) + .map_err(|error| format!("{}: {}", error.code, error.message))? + .into_iter() + .map(|(kind, name)| format!("{kind}:{name}")) + .collect(); + if selected.is_empty() { + return Err("the selectors match no nodes in the current publication".into()); + } + Ok(selected) +} + +/// A published node as the selector graph sees it; keys without a kind are not selectable. +fn graph_resource(resource: &PlannerResource) -> Option { + Some(GraphResource { + key: object_key(&resource.key)?, + deps: resource + .deps + .iter() + .map(String::as_str) + .filter_map(object_key) + .collect(), + tags: resource.tags.clone(), + folder: resource.folder.clone(), + }) +} + +fn object_key(key: &str) -> Option<(String, String)> { + key.split_once(':') + .map(|(kind, name)| (kind.to_owned(), name.to_owned())) +} diff --git a/crates/sqlbuild-cli/src/adhoc/constants.rs b/crates/sqlbuild-cli/src/adhoc/constants.rs new file mode 100644 index 0000000000..81f0d9ff24 --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/constants.rs @@ -0,0 +1,138 @@ +//! Which commands and flags an ad-hoc run accepts; anything not listed is refused. + +/// Verbs a person may plan; workers launch only those the control plane lists. +pub(crate) const ADHOC_VERBS: [&str; 5] = ["build", "test", "seed", "audit", "freshness"]; + +/// The verb whose settings must also form valid job settings. +pub(crate) const BUILD_VERB: &str = "build"; + +/// The parser destination holding the verb itself, which is not a setting. +pub(crate) const COMMAND_DEST: &str = "command"; + +/// The destination `--load` and `--no-load` share; only `--no-load` is accepted. +pub(crate) const LOAD_SOURCES_DEST: &str = "load_sources"; + +/// Parser destinations that carry selectors, resolved to exact node keys on the server. +pub(crate) const SELECTOR_DESTS: [&str; 3] = ["select", "exclude", "positional_select"]; + +/// Destinations an ad-hoc run may set; the control plane gates history rewrites and warehouses. +pub(crate) const ALLOWED_DESTS: [&str; 27] = [ + "max_microbatches", + "run_tests", + "run_audits", + "include_python", + "fail_fast", + "concurrency", + "test_timeout", + "selection_diagnostics", + "verbose", + "case", + "inspect", + "state", + "fail_on_error", + "fail_on_stale", + "full_refresh", + "start_cursor", + "end_cursor", + "start_cursor_ts", + "end_cursor_ts", + "start_cursor_int", + "end_cursor_int", + "allow_snapshot_full_refresh", + "allow_snapshot_schema_change", + "allow_table_type_downgrade", + "allow_retention_decrease", + "warehouse", + "load_sources", +]; + +/// Parser converters that take whole numbers; the table names them as Python does. +pub(crate) const INTEGER_CONVERTERS: [&str; 2] = ["int", POSITIVE_INTEGER_CONVERTER]; + +/// The converter that also refuses zero and negative numbers. +pub(crate) const POSITIVE_INTEGER_CONVERTER: &str = "_positive_integer"; + +/// Refused destinations: the flag people typed and why an ad-hoc run cannot honour it. +pub(crate) const REFUSED_DESTS: [(&str, &str, &str); 25] = [ + ( + "select_file", + "--select-file", + "it reads a file on the worker", + ), + ( + "reload", + "--reload", + "source loads are not part of ad-hoc runs yet", + ), + ( + "allow_missing_migration_origin", + "--allow-missing-migration-origin", + "it can lose data", + ), + ("vars", "--vars", "variables change the published SQL"), + ("defer_to", "--defer-to", "it reads another target"), + ( + "defer_sources_to", + "--defer-sources-to", + "it reads another target", + ), + ( + "defer_clone_from", + "--defer-clone-from", + "it clones from another target", + ), + ( + "no_sql_validation", + "--no-sql-analysis", + "workers always verify the published SQL", + ), + ("no_cache", "--no-cache", "workers manage their own cache"), + ( + "target", + "--target", + "the run's target is the one you are viewing", + ), + ( + "project_dir", + "--project-dir", + "workers run the published project", + ), + ( + "dbt_project_dir", + "--dbt-project-dir", + "workers run the published project", + ), + ( + "dbt_profiles_dir", + "--dbt-profiles-dir", + "workers run the published project", + ), + ( + "dbt_target", + "--dbt-target", + "workers run the published project", + ), + ( + "dbt_target_path", + "--dbt-target-path", + "workers run the published project", + ), + ("json", "--json", "workers own the run's output"), + ( + "json_output", + "--json-output", + "workers own the run's output", + ), + ( + "event_output", + "--event-output", + "workers own the run's output", + ), + ("manifest", "--manifest", "workers own the run's output"), + ("no_color", "--no-color", "workers own the run's output"), + ("debug", "--debug", "it prints executed SQL"), + ("help", "--help", "help is not a run"), + ("version", "--version", "it is not a run"), + ("since", "--since", "it needs the worker's Git checkout"), + ("explain", "--explain", "it needs the worker's Git checkout"), +]; diff --git a/crates/sqlbuild-cli/src/adhoc/main/adhoc_planner.rs b/crates/sqlbuild-cli/src/adhoc/main/adhoc_planner.rs new file mode 100644 index 0000000000..64203a9c8f --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/main/adhoc_planner.rs @@ -0,0 +1,13 @@ +//! The command planner `sqb server` resolves ad-hoc runs with. + +use std::sync::Arc; + +use sqlbuild_orchestration::runs::types::CommandPlanner; + +use crate::adhoc::_helpers::planner::CliPlanner; + +/// The CLI's grammar, flag allowlist and selector resolver behind the control plane's planner contract. +#[must_use] +pub fn adhoc_planner() -> Arc { + Arc::new(CliPlanner) +} diff --git a/crates/sqlbuild-cli/src/adhoc/main/mod.rs b/crates/sqlbuild-cli/src/adhoc/main/mod.rs new file mode 100644 index 0000000000..64db9001ca --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/main/mod.rs @@ -0,0 +1 @@ +pub mod adhoc_planner; diff --git a/crates/sqlbuild-cli/src/adhoc/mod.rs b/crates/sqlbuild-cli/src/adhoc/mod.rs new file mode 100644 index 0000000000..d22d23c1d3 --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/mod.rs @@ -0,0 +1,8 @@ +//! Ad-hoc commands for `sqb server`: the CLI's own grammar, a flag allowlist and selectors resolved over a publication. + +pub(crate) mod _helpers; +pub(crate) mod constants; +pub mod main; + +#[cfg(test)] +mod tests; diff --git a/crates/sqlbuild-cli/src/adhoc/tests.rs b/crates/sqlbuild-cli/src/adhoc/tests.rs new file mode 100644 index 0000000000..f06a26c26a --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/tests.rs @@ -0,0 +1,6 @@ +#[path = "tests/test_adhoc_planner.rs"] +mod adhoc_planner; +#[path = "tests/helpers.rs"] +mod helpers; +#[path = "tests/test_types.rs"] +mod test_types; diff --git a/crates/sqlbuild-cli/src/adhoc/tests/helpers.rs b/crates/sqlbuild-cli/src/adhoc/tests/helpers.rs new file mode 100644 index 0000000000..0725ac9380 --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/tests/helpers.rs @@ -0,0 +1,27 @@ +use serde_json::Value; +use sqlbuild_orchestration::runs::models::PlannerResource; + +/// A published node as the planner receives it from the control plane. +pub(super) fn resource( + key: &str, + deps: &[&str], + tags: &[&str], + folder: Option<&str>, +) -> PlannerResource { + PlannerResource { + key: key.into(), + deps: deps.iter().map(|dep| (*dep).to_owned()).collect(), + tags: tags.iter().map(|tag| (*tag).to_owned()).collect(), + folder: folder.map(str::to_owned), + } +} + +/// An expected plan in the planner's owned form, so the case compares whole results. +pub(super) fn owned_plan( + expected: &Result<(&[&str], Value), &str>, +) -> Result<(Vec, Value), String> { + expected + .clone() + .map(|(keys, settings)| (keys.iter().map(|key| (*key).to_owned()).collect(), settings)) + .map_err(str::to_owned) +} diff --git a/crates/sqlbuild-cli/src/adhoc/tests/test_adhoc_planner.rs b/crates/sqlbuild-cli/src/adhoc/tests/test_adhoc_planner.rs new file mode 100644 index 0000000000..03f1a5aeea --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/tests/test_adhoc_planner.rs @@ -0,0 +1,214 @@ +//! The planner parses with the CLI's own table, refuses every flag outside the allowlist and resolves exact keys. + +use serde_json::json; + +use crate::adhoc::main::adhoc_planner::adhoc_planner; +use crate::adhoc::tests::helpers::{owned_plan, resource}; +use crate::adhoc::tests::test_types::AdhocPlanTestCase; + +#[test] +fn given_ad_hoc_commands_when_planned_then_flags_follow_the_allowlist_and_selectors_resolve_exactly() + { + let resources = [ + resource( + "model:orders", + &["source:shop.raw_orders", "seed:currencies"], + &["daily"], + Some("models/marts"), + ), + resource( + "model:order_totals", + &["model:orders"], + &["daily"], + Some("models/marts"), + ), + resource("model:customers", &[], &[], Some("models/staging")), + resource("source:shop.raw_orders", &[], &[], None), + resource("seed:currencies", &[], &[], None), + ]; + let test_cases = [ + AdhocPlanTestCase { + description: "upstream selector reaches the source and seed it reads", + command: &["build", "--select", "+orders"], + expected_plan: Ok(( + &["model:orders", "seed:currencies", "source:shop.raw_orders"], + json!({}), + )), + }, + AdhocPlanTestCase { + description: "positional selectors join --select, and --exclude removes", + command: &[ + "build", + "tag:daily", + "-s", + "customers", + "--exclude", + "order_totals", + ], + expected_plan: Ok((&["model:customers", "model:orders"], json!({}))), + }, + AdhocPlanTestCase { + description: "no selector builds every node, as on the command line", + command: &["build", "--fail-fast"], + expected_plan: Ok(( + &[ + "model:customers", + "model:order_totals", + "model:orders", + "seed:currencies", + "source:shop.raw_orders", + ], + json!({"fail_fast": true}), + )), + }, + AdhocPlanTestCase { + description: "an abbreviated flag is the same flag, so it is still detected", + command: &["build", "-s", "orders", "--full-ref"], + expected_plan: Ok((&["model:orders"], json!({"full_refresh": true}))), + }, + AdhocPlanTestCase { + description: "store-false flags and gated flags become settings by destination", + command: &[ + "build", + "-s", + "customers", + "--no-tests", + "--no-load", + "--warehouse", + "BUILD_XS", + "--concurrency", + "4", + ], + expected_plan: Ok(( + &["model:customers"], + json!({"concurrency": 4, "load_sources": false, "run_tests": false, "warehouse": "BUILD_XS"}), + )), + }, + AdhocPlanTestCase { + description: "cursor flags pass through for the server to gate", + command: &[ + "build", + "-s", + "customers", + "--start-cursor-int", + "1", + "--end-cursor-int", + "9", + ], + expected_plan: Ok(( + &["model:customers"], + json!({"end_cursor_int": "9", "start_cursor_int": "1"}), + )), + }, + AdhocPlanTestCase { + description: "path selectors use the published folder", + command: &["build", "-s", "path:models/staging"], + expected_plan: Ok((&["model:customers"], json!({}))), + }, + AdhocPlanTestCase { + description: "variables are refused", + command: &["build", "--vars", "{\"day\": 1}"], + expected_plan: Err("--vars is not available for ad-hoc runs"), + }, + AdhocPlanTestCase { + description: "a flag outside the allowlist is refused by the name people typed", + command: &["build", "--changed"], + expected_plan: Err("--changed is not available for ad-hoc runs"), + }, + AdhocPlanTestCase { + description: "a selector file is refused", + command: &["build", "--select-file", "selection.txt"], + expected_plan: Err("--select-file is not available"), + }, + AdhocPlanTestCase { + description: "loading sources is refused", + command: &["build", "--load"], + expected_plan: Err("--load is not available"), + }, + AdhocPlanTestCase { + description: "another target is refused", + command: &["build", "--target", "production"], + expected_plan: Err("--target is not available"), + }, + AdhocPlanTestCase { + description: "debug output would print executed SQL", + command: &["build", "--debug"], + expected_plan: Err("--debug is not available"), + }, + AdhocPlanTestCase { + description: "deferral reads another target", + command: &["build", "--defer-to", "production"], + expected_plan: Err("--defer-to is not available"), + }, + AdhocPlanTestCase { + description: "a missing migration origin can lose data", + command: &["build", "--allow-missing-migration-origin"], + expected_plan: Err("--allow-missing-migration-origin is not available"), + }, + AdhocPlanTestCase { + description: "an unknown flag fails the CLI's own parse", + command: &["build", "--everything"], + expected_plan: Err("unrecognized arguments: --everything"), + }, + AdhocPlanTestCase { + description: "concurrency must be positive", + command: &["build", "--concurrency", "0"], + expected_plan: Err("--concurrency must be >= 1"), + }, + AdhocPlanTestCase { + description: "the build parser's own cursor rules still apply", + command: &["build", "--start-cursor", "1", "--start-cursor-int", "1"], + expected_plan: Err("cannot be combined with the -ts or -int cursor flags"), + }, + AdhocPlanTestCase { + description: "an unknown name is the CLI's selector error", + command: &["build", "-s", "returns"], + expected_plan: Err("returns"), + }, + AdhocPlanTestCase { + description: "loads are not ad-hoc runs yet", + command: &["load"], + expected_plan: Err("sqb load is not available as an ad-hoc run"), + }, + AdhocPlanTestCase { + description: "commands that are not runs are refused", + command: &["server"], + expected_plan: Err("sqb server is not available as an ad-hoc run"), + }, + AdhocPlanTestCase { + description: "other verbs plan with their own parser", + command: &["test", "-s", "orders", "--case", "happy_path"], + expected_plan: Ok((&["model:orders"], json!({"case": "happy_path"}))), + }, + ]; + let planner = adhoc_planner(); + for test_case in test_cases { + let command: Vec = test_case + .command + .iter() + .map(|token| (*token).to_owned()) + .collect(); + let actual = planner + .plan(&command, &resources) + .map(|plan| { + ( + plan.selection.into_iter().collect::>(), + json!(plan.settings), + ) + }) + .map_err(|error| { + test_case + .expected_plan + .as_ref() + .err() + .filter(|fragment| error.contains(**fragment)) + .map_or(error.clone(), |fragment| (*fragment).to_owned()) + }); + assert_eq!( + actual, + owned_plan(&test_case.expected_plan), + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlbuild-cli/src/adhoc/tests/test_types.rs b/crates/sqlbuild-cli/src/adhoc/tests/test_types.rs new file mode 100644 index 0000000000..dec62a7bb3 --- /dev/null +++ b/crates/sqlbuild-cli/src/adhoc/tests/test_types.rs @@ -0,0 +1,8 @@ +//! Table rows for the ad-hoc planner. + +/// A command, and the exact keys and settings it plans to, or a fragment of its refusal. +pub(super) struct AdhocPlanTestCase { + pub(super) description: &'static str, + pub(super) command: &'static [&'static str], + pub(super) expected_plan: Result<(&'static [&'static str], serde_json::Value), &'static str>, +} diff --git a/crates/sqlbuild-cli/src/lib.rs b/crates/sqlbuild-cli/src/lib.rs index ce5297b160..6fd859c0bb 100644 --- a/crates/sqlbuild-cli/src/lib.rs +++ b/crates/sqlbuild-cli/src/lib.rs @@ -2,6 +2,7 @@ #![forbid(unsafe_code)] +pub mod adhoc; pub mod argv; pub mod compute_logs; pub mod cost_store; diff --git a/crates/sqlbuild-cli/src/native_commands/_helpers/execution.rs b/crates/sqlbuild-cli/src/native_commands/_helpers/execution.rs index 406d840032..0b9f3ae3e4 100644 --- a/crates/sqlbuild-cli/src/native_commands/_helpers/execution.rs +++ b/crates/sqlbuild-cli/src/native_commands/_helpers/execution.rs @@ -11,9 +11,9 @@ use serde_json::json; use sqlbuild_orchestration::api::constants::BODY_BYTES; use sqlbuild_orchestration::api::errors::ClientError; use sqlbuild_orchestration::api::main::serve_api::serve_api; -use sqlbuild_orchestration::api::models::{ - ApiAuthentication, DevelopmentScope, DockerImageSource, HumanLogin, -}; + +use crate::adhoc::main::adhoc_planner::adhoc_planner; +use sqlbuild_orchestration::api::models::{ApiAuthentication, DevelopmentScope, HumanLogin}; use sqlbuild_orchestration::credentials::models::AuditRetention; use sqlbuild_orchestration::platform::constants::PLATFORM_WEBHOOK_VARIABLE; use sqlbuild_orchestration::platform::main::report_remote_location_health::report_remote_location_health; @@ -22,6 +22,7 @@ use sqlbuild_orchestration::platform::models::{ LocationHealth, LocationHealthReport, LocationWorkClient, LocationWorkPoll, WarehouseSyncFailure, }; +use sqlbuild_orchestration::runs::types::RunHandoff; use sqlbuild_orchestration::scheduler::main::run_scheduler::run_scheduler; use sqlbuild_orchestration::scheduler::models::SchedulerState; use sqlbuild_orchestration::snapshots::main::next_remote_generation::next_remote_generation; @@ -111,7 +112,7 @@ pub(crate) async fn run_development_roles( (ApiAuthentication { login: HumanLogin::Development(scope), ..ApiAuthentication::default() - }, Vec::new(), Default::default()), + }, (Vec::new(), adhoc_planner()), Default::default()), (move || { drop(write_output(&[(Stream::Stderr, &format!( "Development API ready: {address}\nDevelopment location: {location_id}\nAuthentication disabled on loopback; triggers {}\n", @@ -814,7 +815,7 @@ pub(crate) async fn open_control_store( /// Decode privileged CLI configuration before handing process ownership to the workers domain. pub(crate) fn open_control_workers( namespace: &Namespace, -) -> Result<(Workers, Vec), ControlError> { +) -> Result<(Workers, RunHandoff), ControlError> { let workers = open_workers( text(namespace, "control_launchers").as_deref(), text(namespace, "control_database_env") @@ -822,7 +823,7 @@ pub(crate) fn open_control_workers( !flag(namespace, "control_no_scheduler"), )?; let images = docker_image_sources(&workers); - Ok((workers, images)) + Ok((workers, (images, adhoc_planner()))) } pub(crate) async fn shutdown_signal() -> Result<(), ControlError> { diff --git a/crates/sqlbuild-cli/src/native_commands/main/_run_control.rs b/crates/sqlbuild-cli/src/native_commands/main/_run_control.rs index d5a88fd9d5..673283718f 100644 --- a/crates/sqlbuild-cli/src/native_commands/main/_run_control.rs +++ b/crates/sqlbuild-cli/src/native_commands/main/_run_control.rs @@ -29,7 +29,7 @@ pub(crate) async fn run_control( let address = control_address(namespace, standalone)?; let (authentication, (statistics, audit, snapshots)) = control_api_settings(namespace)?; let store = open_control_store(namespace).await?; - let (workers, images) = open_control_workers(namespace)?; + let (workers, handoff) = open_control_workers(namespace)?; let (stop, shutdown) = watch::channel(false); let mut tasks = tokio::task::JoinSet::new(); if let Some(address) = address { @@ -42,7 +42,7 @@ pub(crate) async fn run_control( tasks.spawn(serve_api( store.clone(), listener, - (authentication, images, statistics), + (authentication, handoff, statistics), ( move || { drop(write_output(&[( diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/authentication/password_sessions.rs b/crates/sqlbuild-orchestration/src/api/_helpers/authentication/password_sessions.rs index 5bce2d5b43..1ab8657bc6 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/authentication/password_sessions.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/authentication/password_sessions.rs @@ -65,14 +65,31 @@ pub(crate) fn person_routes() -> Router { "/api/admin/people/{principal_id}", post(crate::api::_helpers::resources::administrators::change), ) + .route( + "/api/admin/people", + get(crate::api::_helpers::resources::administrators::people), + ) + .route( + "/api/admin/locations/{location_id}/targets/{target}/roles", + get(crate::api::_helpers::resources::administrators::definitions), + ) .route( "/api/admin/roles", - post(crate::api::_helpers::resources::administrators::assign), + post(crate::api::_helpers::resources::administrators::assign) + .get(crate::api::_helpers::resources::administrators::holdings), ) .route( "/api/control/runs", post(crate::api::_helpers::resources::person_operations::request_job), ) + .route( + "/api/control/runs/adhoc", + post(crate::api::_helpers::resources::person_operations::request_adhoc), + ) + .route( + "/api/control/runs/adhoc/check", + post(crate::api::_helpers::resources::person_operations::check_adhoc), + ) .route( "/api/control/runs/cancel", post(crate::api::_helpers::resources::person_operations::cancel_run), @@ -121,6 +138,14 @@ pub(crate) fn person_routes() -> Router { "/api/control/locations/{location_id}/targets/{target}/snapshots", get(crate::api::_helpers::resources::person_reads::snapshots), ) + .route( + "/api/control/locations/{location_id}/targets/{target}/health", + get(crate::api::_helpers::resources::person_reads::health), + ) + .route( + "/api/control/platform/problems", + get(crate::api::_helpers::resources::person_reads::problems), + ) .route( "/api/control/locations/{location_id}/targets/{target}/catalog", get(crate::api::_helpers::resources::person_operations::catalog), @@ -137,6 +162,26 @@ pub(crate) fn person_routes() -> Router { "/api/control/locations/{location_id}/targets/{target}/node-status", get(crate::api::_helpers::resources::person_operations::node_status), ) + .route( + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/events", + get(crate::api::_helpers::resources::person_reads::run_events), + ) + .route( + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements", + get(crate::api::_helpers::resources::person_reads::run_statements), + ) + .route( + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements/sql", + get(crate::api::_helpers::resources::person_reads::run_statement_sql), + ) + .route( + "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/output", + get(crate::api::_helpers::resources::person_reads::run_output), + ) + .route( + "/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/runs", + get(crate::api::_helpers::resources::person_reads::node_runs), + ) .route( "/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/sql", get(crate::api::_helpers::resources::person_node_sql::read), diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/administrators.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/administrators.rs index 89fbada6a9..72c7bc9589 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/resources/administrators.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/administrators.rs @@ -8,8 +8,14 @@ use crate::api::errors::ApiError; use crate::api::models::MutationResponse; use crate::authorization::main::assign_builtin_role::assign_builtin_role; use crate::authorization::main::change_administrator::change_administrator; +use crate::authorization::main::list_role_definitions::list_role_definitions; +use crate::authorization::main::list_role_holdings::list_role_holdings; +use crate::credentials::main::identities::list_people::list_people; use crate::credentials::models::AuthenticatedPerson; -use crate::credentials::models::{AdministratorChange, AuditContext, RoleAssignmentRequest}; +use crate::credentials::models::{ + AdministratorChange, AuditContext, PersonSummary, RoleAssignmentRequest, RoleHoldings, + RoleOverview, +}; use crate::store::models::ControlStore; #[utoipa::path(post, path = "/api/admin/people/{principal_id}", operation_id = "changeAdministrator", @@ -42,3 +48,42 @@ pub(crate) async fn assign( applied: assign_builtin_role(&store, (&context, principal), &request).await?, })) } + +#[utoipa::path(get, path = "/api/admin/people", operation_id = "listPeople", + responses((status = 200, body = Vec), (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn people( + (State(store), Extension(context)): (State, Extension), + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result>, ApiError> { + Ok(Json( + list_people(&store, (&context, principal), chrono::Utc::now()).await?, + )) +} + +#[utoipa::path(get, path = "/api/admin/roles", operation_id = "listRoleHoldings", + responses((status = 200, body = RoleHoldings), (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn holdings( + (State(store), Extension(context)): (State, Extension), + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result, ApiError> { + Ok(Json( + list_role_holdings(&store, (&context, principal)).await?, + )) +} + +#[utoipa::path(get, path = "/api/admin/locations/{location_id}/targets/{target}/roles", operation_id = "listRoleDefinitions", + params(("location_id" = Uuid, Path), ("target" = String, Path)), + responses((status = 200, body = Vec, description = "Built-in roles, then the target's published custom roles"), + (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn definitions( + (State(store), Extension(context)): (State, Extension), + Path((location_id, target)): Path<(Uuid, String)>, + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result>, ApiError> { + Ok(Json( + list_role_definitions(&store, (&context, principal), (location_id, &target)).await?, + )) +} diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/audit.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/audit.rs index 2ec377e95e..1cf398f92d 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/resources/audit.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/audit.rs @@ -11,7 +11,8 @@ use crate::credentials::models::{AuditContext, SecurityAuditEvent}; use crate::store::models::ControlStore; #[utoipa::path(get, path = "/api/control/audit", operation_id = "readSecurityAudit", - params(("after" = Option, Query, description = "Exclusive storage-order cursor"), ("limit" = Option, Query)), + params(("after" = Option, Query, description = "Exclusive storage-order cursor"), + ("before" = Option, Query, description = "Exclusive upper cursor; when set, the page is newest first"), ("limit" = Option, Query)), responses((status = 200, body = Vec), (status = 400, body = crate::api::models::ApiErrorBody), (status = 401, description = "Session rejected"), (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] @@ -24,7 +25,10 @@ pub(crate) async fn read( read_security_audit( &store, (&context, principal), - (options.after.unwrap_or(0), options.limit.unwrap_or(100)), + ( + (options.after.unwrap_or(0), options.before), + options.limit.unwrap_or(100), + ), ) .await?, )) diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/mod.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/mod.rs index a4a82ccea6..5b8f666c47 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/resources/mod.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/mod.rs @@ -12,6 +12,7 @@ pub(crate) mod person_maintenance; pub(crate) mod person_node_sql; pub(crate) mod person_operations; pub(crate) mod person_reads; +pub(crate) mod request_json; pub(crate) mod router; pub(crate) mod run_reads; pub(crate) mod runs; diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_operations.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_operations.rs index 02fcaff030..0bc0faabba 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_operations.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_operations.rs @@ -1,5 +1,7 @@ //! Person operations use person sessions, never the machine-resource bearer router. +use std::sync::Arc; + use axum::extract::{Path, Query, State}; use axum::{Extension, Json}; use uuid::Uuid; @@ -10,11 +12,15 @@ use crate::credentials::models::AuthenticatedPerson; use crate::credentials::models::{AuditContext, DefaultRole}; use crate::declarations::main::change_person_trigger::change_person_trigger; use crate::declarations::models::TriggerChangeRequest; -use crate::runs::main::cancel_person_run::cancel_person_run; +use crate::runs::main::check_adhoc_run::check_adhoc_run; use crate::runs::main::list_person_runs::list_person_runs; -use crate::runs::main::request_person_job_run::request_person_job_run; +use crate::runs::main::request_person_run::request_person_run; use crate::runs::main::request_rerun::request_rerun; -use crate::runs::models::{CancelRunRequest, JobRunRequest, RerunRequest, RunRecord}; +use crate::runs::models::{ + AdhocCheck, AdhocCheckRequest, AdhocRunRequest, CancelRunRequest, JobRunRequest, + PersonRunRequest, RerunRequest, RunRecord, +}; +use crate::runs::types::CommandPlanner; use crate::store::models::ControlStore; use crate::warehouse_metadata::main::read_person_catalog::read_person_catalog; use crate::warehouse_metadata::main::read_person_fingerprint::read_person_fingerprint; @@ -64,10 +70,55 @@ pub(crate) async fn request_job( Json(request): Json, ) -> Result, ApiError> { Ok(Json(MutationResponse { - applied: request_person_job_run(&store, (&context, principal), &request).await?, + applied: request_person_run( + &store, + (&context, principal), + PersonRunRequest::Job(&request), + ) + .await?, })) } +#[utoipa::path(post, path = "/api/control/runs/adhoc", operation_id = "requestAdhocRun", + params(("x-sqb-csrf" = String, Header)), request_body = AdhocRunRequest, + responses((status = 200, body = MutationResponse), (status = 400, body = crate::api::models::ApiErrorBody), + (status = 401, description = "Session rejected"), (status = 403, body = crate::api::models::ApiErrorBody)), + security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn request_adhoc( + (State(store), Extension(context)): (State, Extension), + (Extension(AuthenticatedPerson(principal, _)), Extension(planner)): ( + Extension, + Extension>, + ), + Json(request): Json, +) -> Result, ApiError> { + Ok(Json(MutationResponse { + applied: request_person_run( + &store, + (&context, principal), + PersonRunRequest::Adhoc(planner.as_ref(), &request), + ) + .await?, + })) +} + +#[utoipa::path(post, path = "/api/control/runs/adhoc/check", operation_id = "checkAdhocRun", + params(("x-sqb-csrf" = String, Header)), request_body = AdhocCheckRequest, + responses((status = 200, body = AdhocCheck), (status = 401, description = "Session rejected")), + security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn check_adhoc( + (State(store), Extension(context)): (State, Extension), + (Extension(AuthenticatedPerson(principal, _)), Extension(planner)): ( + Extension, + Extension>, + ), + Json(request): Json, +) -> Result, ApiError> { + Ok(Json( + check_adhoc_run(&store, (&context, principal), planner.as_ref(), &request).await?, + )) +} + #[utoipa::path(post, path = "/api/control/runs/cancel", operation_id = "cancelPersonRun", params(("x-sqb-csrf" = String, Header)), request_body = CancelRunRequest, responses((status = 200, body = MutationResponse), (status = 401, description = "Session rejected"), @@ -78,7 +129,12 @@ pub(crate) async fn cancel_run( Json(request): Json, ) -> Result, ApiError> { Ok(Json(MutationResponse { - applied: cancel_person_run(&store, (&context, principal), &request).await?, + applied: request_person_run( + &store, + (&context, principal), + PersonRunRequest::Cancel(&request), + ) + .await?, })) } diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_reads.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_reads.rs index aa10e0c867..b8854f4d1f 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_reads.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/person_reads.rs @@ -1,4 +1,4 @@ -//! Person reads of run nodes, catalog nodes, jobs, triggers, ticks and publications; refusal precedes every lookup, so unknown objects look forbidden. +//! Person reads of run nodes, node run history, catalog nodes, jobs, triggers, ticks and publications; refusal precedes every lookup, so unknown objects look forbidden. use axum::extract::{Path, Query, State}; use axum::http::StatusCode; @@ -6,18 +6,28 @@ use axum::{Extension, Json}; use uuid::Uuid; use crate::api::errors::ApiError; -use crate::api::models::ReadQuery; +use crate::api::models::{ReadQuery, RunEventsQuery, StatementSqlQuery}; use crate::credentials::models::{AuditContext, AuthenticatedPerson, DefaultRole}; use crate::declarations::main::list_person_jobs::list_person_jobs; use crate::declarations::main::list_person_triggers::list_person_triggers; -use crate::declarations::models::{JobRecord, TriggerRecord}; +use crate::declarations::models::{JobRecord, PersonTriggerRecord}; +use crate::platform::main::read_person_location_health::read_person_location_health; +use crate::platform::main::read_person_problems::read_person_problems; +use crate::platform::models::{LocationHealthObservation, PlatformProblems}; use crate::runs::main::list_person_run_nodes::list_person_run_nodes; -use crate::runs::models::RunNodeRecord; +use crate::runs::models::{ + NodeRunRecord, RunEventRecord, RunNodeRecord, RunOutput, RunStatementRecord, RunStatementSql, +}; use crate::scheduler::main::list_person_ticks::list_person_ticks; use crate::scheduler::models::TickRecord; use crate::snapshots::main::list_person_snapshots::list_person_snapshots; use crate::snapshots::models::PublishedSnapshot; use crate::store::models::ControlStore; +use crate::warehouse_history::main::read_person_node_runs::read_person_node_runs; +use crate::warehouse_history::main::read_person_run_events::read_person_run_events; +use crate::warehouse_history::main::read_person_run_output::read_person_run_output; +use crate::warehouse_history::main::read_person_run_statement_sql::read_person_run_statement_sql; +use crate::warehouse_history::main::read_person_run_statements::read_person_run_statements; use crate::warehouse_metadata::main::list_person_catalog_nodes::list_person_catalog_nodes; use crate::warehouse_metadata::models::CatalogNodeRecord; @@ -66,6 +76,143 @@ pub(crate) async fn nodes( )) } +#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/runs", operation_id = "listPersonNodeRuns", + params(("location_id" = Uuid, Path), ("target" = String, Path), ("node_key" = String, Path), + ("limit" = Option, Query, description = "Maximum recent runs; defaults to 100, capped at 1000")), + responses((status = 200, body = Vec, description = "Runs that built the node, newest first"), + (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn node_runs( + (State(store), Extension(context)): (State, Extension), + Extension(default_role): Extension, + (Path((location_id, target, node_key)), Query(options)): ( + Path<(Uuid, String, String)>, + Query, + ), + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result>, ApiError> { + Ok(Json( + read_person_node_runs( + &store, + (&context, principal), + (location_id, &target, &node_key), + (options.limit.unwrap_or(100), default_role), + ) + .await?, + )) +} + +#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/events", operation_id = "listPersonRunEvents", + params(("location_id" = Uuid, Path), ("target" = String, Path), ("run_id" = Uuid, Path), + ("after" = Option, Query, description = "Return events after this storage cursor"), + ("limit" = Option, Query, description = "Maximum events; defaults to 1000, capped at 5000")), + responses((status = 200, body = Vec, description = "Event metadata oldest first, without payloads"), + (status = 401, description = "Session rejected"), (status = 403, body = crate::api::models::ApiErrorBody), + (status = 404, description = "Run not found for a person who may view the whole target")), + security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn run_events( + (State(store), Extension(context)): (State, Extension), + Extension(default_role): Extension, + (Path((location_id, target, run_id)), Query(options)): ( + Path<(Uuid, String, Uuid)>, + Query, + ), + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result>, ApiError> { + read_person_run_events( + &store, + (&context, principal), + ( + (location_id, &target, run_id), + (options.after.unwrap_or(0), options.limit.unwrap_or(1000)), + ), + default_role, + ) + .await? + .map(Json) + .ok_or_else(|| not_found("run not found")) +} + +#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements", operation_id = "listPersonRunStatements", + params(("location_id" = Uuid, Path), ("target" = String, Path), ("run_id" = Uuid, Path), + ("limit" = Option, Query, description = "Maximum statements; defaults to 1000, capped at 5000")), + responses((status = 200, body = Vec, description = "Statement metadata in start order, without SQL"), + (status = 401, description = "Session rejected"), (status = 403, body = crate::api::models::ApiErrorBody), + (status = 404, description = "Run not found for a person who may view the whole target")), + security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn run_statements( + (State(store), Extension(context)): (State, Extension), + Extension(default_role): Extension, + (Path((location_id, target, run_id)), Query(options)): ( + Path<(Uuid, String, Uuid)>, + Query, + ), + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result>, ApiError> { + read_person_run_statements( + &store, + (&context, principal), + ( + (location_id, &target, run_id), + options.limit.unwrap_or(1000), + ), + default_role, + ) + .await? + .map(Json) + .ok_or_else(|| not_found("run not found")) +} + +#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements/sql", operation_id = "readPersonRunStatementSql", + params(("location_id" = Uuid, Path), ("target" = String, Path), ("run_id" = Uuid, Path), + ("statement_id" = String, Query, description = "One statement of this run")), + responses((status = 200, body = RunStatementSql), (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody, description = "Needs view.sql on every node the run selected"), + (status = 404, description = "Run or statement not found")), + security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn run_statement_sql( + (State(store), Extension(context)): (State, Extension), + Extension(default_role): Extension, + (Path((location_id, target, run_id)), Query(options)): ( + Path<(Uuid, String, Uuid)>, + Query, + ), + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result, ApiError> { + read_person_run_statement_sql( + &store, + (&context, principal), + ((location_id, &target, run_id), &options.statement_id), + default_role, + ) + .await? + .map(Json) + .ok_or_else(|| not_found("statement not found")) +} + +#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/output", operation_id = "readPersonRunOutput", + params(("location_id" = Uuid, Path), ("target" = String, Path), ("run_id" = Uuid, Path)), + responses((status = 200, body = RunOutput), (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody, description = "Needs view.evidence on every node the run selected"), + (status = 404, description = "Run not found for a person who may view the whole target")), + security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn run_output( + (State(store), Extension(context)): (State, Extension), + Extension(default_role): Extension, + Path((location_id, target, run_id)): Path<(Uuid, String, Uuid)>, + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result, ApiError> { + read_person_run_output( + &store, + (&context, principal), + (location_id, &target, run_id), + default_role, + ) + .await? + .map(Json) + .ok_or_else(|| not_found("run not found")) +} + #[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/jobs", operation_id = "listPersonJobs", params(("location_id" = Uuid, Path), ("target" = String, Path)), responses((status = 200, body = Vec), (status = 401, description = "Session rejected"), @@ -89,14 +236,14 @@ pub(crate) async fn jobs( #[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/triggers", operation_id = "listPersonTriggers", params(("location_id" = Uuid, Path), ("target" = String, Path)), - responses((status = 200, body = Vec), (status = 401, description = "Session rejected"), + responses((status = 200, body = Vec, description = "Published triggers with their newest tick"), (status = 401, description = "Session rejected"), (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] pub(crate) async fn triggers( (State(store), Extension(context)): (State, Extension), Extension(default_role): Extension, Path((location_id, target)): Path<(Uuid, String)>, Extension(AuthenticatedPerson(principal, _)): Extension, -) -> Result>, ApiError> { +) -> Result>, ApiError> { Ok(Json( list_person_triggers( &store, @@ -161,6 +308,41 @@ pub(crate) async fn snapshots( )) } +#[utoipa::path(get, path = "/api/control/locations/{location_id}/targets/{target}/health", operation_id = "readPersonLocationHealth", + params(("location_id" = Uuid, Path), ("target" = String, Path)), + responses((status = 200, body = Vec, description = "This target's newest health observations and instance check-ins"), + (status = 401, description = "Session rejected"), + (status = 403, body = crate::api::models::ApiErrorBody)), security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn health( + (State(store), Extension(context)): (State, Extension), + Extension(default_role): Extension, + Path((location_id, target)): Path<(Uuid, String)>, + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result>, ApiError> { + Ok(Json( + read_person_location_health( + &store, + (&context, principal), + (location_id, &target), + default_role, + ) + .await?, + )) +} + +#[utoipa::path(get, path = "/api/control/platform/problems", operation_id = "readPersonPlatformProblems", + responses((status = 200, body = PlatformProblems, description = "Live scheduler state, and broken locations and late ticks of targets the person lists"), + (status = 401, description = "Session rejected")), security(("session" = []), ("cliSession" = [])))] +pub(crate) async fn problems( + State(store): State, + Extension(default_role): Extension, + Extension(AuthenticatedPerson(principal, _)): Extension, +) -> Result, ApiError> { + Ok(Json( + read_person_problems(&store, principal, default_role).await?, + )) +} + fn not_found(message: &str) -> ApiError { ApiError { status: StatusCode::NOT_FOUND, diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/request_json.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/request_json.rs new file mode 100644 index 0000000000..3956a45d37 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/request_json.rs @@ -0,0 +1,125 @@ +//! JSON request bodies that may arrive gzip-encoded, decoded within a decompressed-size bound. + +use std::io::{BufReader, Read}; + +use axum::http::{HeaderMap, StatusCode, header}; +use flate2::read::MultiGzDecoder; +use serde::de::DeserializeOwned; + +use crate::api::constants::{GZIP_ENCODING, IDENTITY_ENCODING, JSON_MEDIA_TYPE}; +use crate::api::errors::ApiError; +use crate::api::models::BoundedReader; + +/// `body` as JSON after its declared encoding; gzip streams into the parser and stops past `limit` bytes. +pub(crate) fn request_json( + headers: &HeaderMap, + body: &[u8], + limit: usize, +) -> Result { + if !json_content(headers) { + return Err(rejection( + StatusCode::UNSUPPORTED_MEDIA_TYPE, + "expected an application/json body".into(), + )); + } + match encodings(headers)?.as_slice() { + [] => serde_json::from_slice(body).map_err(|error| parse_rejection(&error)), + [encoding] if encoding == GZIP_ENCODING => { + let mut reader = BoundedReader { + inner: MultiGzDecoder::new(body), + remaining: limit, + exceeded: false, + }; + let parsed: Result = + serde_json::from_reader(BufReader::new(&mut reader)); + match parsed { + Ok(value) => Ok(value), + Err(_) if reader.exceeded => Err(rejection( + StatusCode::PAYLOAD_TOO_LARGE, + format!("decompressed body exceeds {limit} bytes"), + )), + Err(error) if error.is_io() => Err(rejection( + StatusCode::BAD_REQUEST, + "request body is not valid gzip".into(), + )), + Err(error) => Err(parse_rejection(&error)), + } + } + _ => Err(rejection( + StatusCode::UNSUPPORTED_MEDIA_TYPE, + "only gzip content encoding is accepted".into(), + )), + } +} + +impl Read for BoundedReader { + fn read(&mut self, buffer: &mut [u8]) -> std::io::Result { + let window = buffer.len().min(self.remaining.saturating_add(1)); + let read = self.inner.read(&mut buffer[..window])?; + if read > self.remaining { + self.exceeded = true; + return Err(std::io::Error::other("decompressed body exceeds its bound")); + } + self.remaining -= read; + Ok(read) + } +} + +fn json_content(headers: &HeaderMap) -> bool { + let Some(value) = headers.get(header::CONTENT_TYPE) else { + return false; + }; + let Ok(text) = value.to_str() else { + return false; + }; + let essence: String = text + .split(';') + .next() + .unwrap_or_default() + .trim() + .to_ascii_lowercase(); + essence == JSON_MEDIA_TYPE + || (essence.starts_with("application/") && essence.ends_with("+json")) +} + +/// Declared encodings other than identity, in order; an unreadable header is refused. +fn encodings(headers: &HeaderMap) -> Result, ApiError> { + let mut encodings: Vec = Vec::new(); + for value in headers.get_all(header::CONTENT_ENCODING) { + let text = value.to_str().map_err(|_| { + rejection( + StatusCode::UNSUPPORTED_MEDIA_TYPE, + "only gzip content encoding is accepted".into(), + ) + })?; + encodings.extend( + text.split(',') + .map(|encoding| encoding.trim().to_ascii_lowercase()) + .filter(|encoding| !encoding.is_empty() && encoding != IDENTITY_ENCODING), + ); + } + Ok(encodings) +} + +/// The statuses axum's JSON extractor uses: malformed JSON is 400, a wrong shape is 422. +fn parse_rejection(error: &serde_json::Error) -> ApiError { + if error.is_data() { + rejection( + StatusCode::UNPROCESSABLE_ENTITY, + format!("Failed to deserialize the JSON body into the target type: {error}"), + ) + } else { + rejection( + StatusCode::BAD_REQUEST, + format!("Failed to parse the request body as JSON: {error}"), + ) + } +} + +fn rejection(status: StatusCode, message: String) -> ApiError { + ApiError { + status, + message, + decision_id: None, + } +} diff --git a/crates/sqlbuild-orchestration/src/api/_helpers/resources/snapshots.rs b/crates/sqlbuild-orchestration/src/api/_helpers/resources/snapshots.rs index 99afc12f8c..c9cb63ced0 100644 --- a/crates/sqlbuild-orchestration/src/api/_helpers/resources/snapshots.rs +++ b/crates/sqlbuild-orchestration/src/api/_helpers/resources/snapshots.rs @@ -1,12 +1,15 @@ //! A location credential publishes and reads only its own location's snapshots. +use axum::body::Bytes; use axum::extract::{Path, State}; -use axum::http::StatusCode; +use axum::http::{HeaderMap, StatusCode}; use axum::{Extension, Json}; use uuid::Uuid; use crate::api::_helpers::authentication::bearer::require_location; use crate::api::_helpers::resources::docker_image; +use crate::api::_helpers::resources::request_json::request_json; +use crate::api::constants::DECOMPRESSED_BODY_BYTES; use crate::api::errors::ApiError; use crate::api::models::{DevelopmentScope, DockerImageSource}; use crate::credentials::models::TokenIdentity; @@ -22,20 +25,30 @@ use crate::store::models::ControlStore; request_body = SnapshotInput, responses((status = 200, body = SnapshotRecord), (status = 400, body = crate::api::models::ApiErrorBody), (status = 401, description = "Invalid credential"), (status = 403, description = "Location not authorized"), - (status = 409, description = "Stale generation")), security(("bearer" = [])))] + (status = 409, description = "Stale generation"), (status = 413, description = "Body over its wire or decompressed bound"), + (status = 415, description = "Content encoding other than gzip")), security(("bearer" = [])))] pub(crate) async fn publish( State(store): State, (Extension(identity), Extension(images)): ( Extension, Extension>>, ), - (Path((location_id, target)), development): ( + (Path((location_id, target)), development, headers): ( Path<(Uuid, String)>, Option>, + HeaderMap, ), - Json(input): Json, + body: Bytes, ) -> Result, ApiError> { require_location(&identity, location_id)?; + let input: SnapshotInput = + tokio::task::spawn_blocking(move || request_json(&headers, &body, DECOMPRESSED_BODY_BYTES)) + .await + .map_err(|_| ApiError { + status: StatusCode::INTERNAL_SERVER_ERROR, + message: "snapshot decoding stopped".into(), + decision_id: None, + })??; if input.location_id != location_id || input.target != target { return Err(ApiError { status: StatusCode::BAD_REQUEST, diff --git a/crates/sqlbuild-orchestration/src/api/constants.rs b/crates/sqlbuild-orchestration/src/api/constants.rs index 9bd3b7c6f9..89bb616723 100644 --- a/crates/sqlbuild-orchestration/src/api/constants.rs +++ b/crates/sqlbuild-orchestration/src/api/constants.rs @@ -1,6 +1,11 @@ //! Shared HTTP transport bounds for the server and native clients. pub const BODY_BYTES: usize = 64 * 1024 * 1024; +/// A gzip body may decompress to this many bytes; a parsed snapshot holds about 7.6 times its JSON size. +pub(crate) const DECOMPRESSED_BODY_BYTES: usize = 320 * 1024 * 1024; +pub(crate) const GZIP_ENCODING: &str = "gzip"; +pub(crate) const IDENTITY_ENCODING: &str = "identity"; +pub(crate) const JSON_MEDIA_TYPE: &str = "application/json"; /// Two 1024-byte passwords can each expand sixfold through JSON escaping. pub(crate) const PASSWORD_BODY_BYTES: usize = 16 * 1024; pub(crate) const PASSWORD_COOKIE: &str = "__Host-sqb-session"; diff --git a/crates/sqlbuild-orchestration/src/api/main/serve_api.rs b/crates/sqlbuild-orchestration/src/api/main/serve_api.rs index 310577350e..29eb042961 100644 --- a/crates/sqlbuild-orchestration/src/api/main/serve_api.rs +++ b/crates/sqlbuild-orchestration/src/api/main/serve_api.rs @@ -7,10 +7,9 @@ use crate::api::_helpers::authentication::{bearer::local_request, configure::con use crate::api::_helpers::resources::ui_assets::{UI_ASSETS, serve_ui}; use crate::api::_helpers::resources::{changes, router::router}; use crate::api::main::openapi_document::openapi_document; -use crate::api::models::{ - ApiAuthentication, ChangeFeed, DevelopmentScope, DockerImageSource, GroupRole, -}; +use crate::api::models::{ApiAuthentication, ChangeFeed, DevelopmentScope, GroupRole}; use crate::authorization::main::publish_group_roles::publish_group_roles; +use crate::runs::types::RunHandoff; use crate::store::errors::ControlError; use crate::store::main::migrate_store::migrate_store; use crate::store::main::subscribe_changes::subscribe_changes; @@ -20,9 +19,9 @@ use crate::warehouse_metadata::models::StatisticsRetention; pub async fn serve_api( store: ControlStore, listener: tokio::net::TcpListener, - (authentication, images, retention): ( + (authentication, (images, planner), retention): ( ApiAuthentication, - Vec, + RunHandoff, StatisticsRetention, ), (ready, shutdown): ( @@ -43,6 +42,7 @@ pub async fn serve_api( let application = router(&store, feed.clone()) .merge(authentication_routes.layer(Extension(feed))) .layer(Extension(std::sync::Arc::new(images))) + .layer(Extension(planner)) .layer(Extension(retention)) .route( "/api/openapi.json", diff --git a/crates/sqlbuild-orchestration/src/api/models.rs b/crates/sqlbuild-orchestration/src/api/models.rs index 9571345a46..6b302ac964 100644 --- a/crates/sqlbuild-orchestration/src/api/models.rs +++ b/crates/sqlbuild-orchestration/src/api/models.rs @@ -14,18 +14,30 @@ use crate::api::_helpers::resources::{ #[openapi(paths( administrators::change, administrators::assign, + administrators::people, + administrators::holdings, + administrators::definitions, audit::read, person_operations::request_job, + person_operations::request_adhoc, + person_operations::check_adhoc, person_operations::cancel_run, person_operations::rerun, person_operations::change_trigger, person_operations::recent_runs, person_reads::run_nodes, person_reads::nodes, + person_reads::node_runs, + person_reads::run_events, + person_reads::run_statements, + person_reads::run_statement_sql, + person_reads::run_output, person_reads::jobs, person_reads::triggers, person_reads::ticks, person_reads::snapshots, + person_reads::health, + person_reads::problems, person_operations::catalog, person_operations::statistics, person_operations::fingerprint, @@ -83,10 +95,24 @@ pub(crate) struct ReadQuery { pub(crate) limit: Option, } +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct RunEventsQuery { + pub(crate) after: Option, + pub(crate) limit: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +pub(crate) struct StatementSqlQuery { + pub(crate) statement_id: String, +} + #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] pub(crate) struct ReadAuditQuery { pub(crate) after: Option, + pub(crate) before: Option, pub(crate) limit: Option, } @@ -431,3 +457,11 @@ pub(crate) struct UiAsset { pub(crate) path: &'static str, pub(crate) body: &'static [u8], } + +/// A decompressing reader that fails once it would yield more than `remaining` bytes. +#[derive(Debug)] +pub(crate) struct BoundedReader { + pub(crate) inner: R, + pub(crate) remaining: usize, + pub(crate) exceeded: bool, +} diff --git a/crates/sqlbuild-orchestration/src/authorization/_helpers/custom_roles.rs b/crates/sqlbuild-orchestration/src/authorization/_helpers/custom_roles.rs index e62b97fb00..f363a71880 100644 --- a/crates/sqlbuild-orchestration/src/authorization/_helpers/custom_roles.rs +++ b/crates/sqlbuild-orchestration/src/authorization/_helpers/custom_roles.rs @@ -35,6 +35,23 @@ fn valid_grant(grant: &RoleGrant) -> bool { !grant.nodes.is_empty() && !blank_selector && !grant.permissions.is_empty() && !administrative } +/// Each published role's description by key; the document was validated when it was published. +pub(crate) fn decode_roles_descriptions( + document: Option<&Value>, +) -> Result, ControlError> { + let roles: BTreeMap = document.map_or_else( + || Ok(BTreeMap::new()), + |document| { + serde_json::from_value(document.clone()) + .map_err(|error| ControlError::Invalid(format!("roles: {error}"))) + }, + )?; + Ok(roles + .into_iter() + .map(|(key, role)| (key, role.description)) + .collect()) +} + pub(crate) fn decode_roles( document: Option<&Value>, ) -> Result>, ControlError> { diff --git a/crates/sqlbuild-orchestration/src/authorization/_helpers/mod.rs b/crates/sqlbuild-orchestration/src/authorization/_helpers/mod.rs index 831d2d7b1e..2e33ac15ba 100644 --- a/crates/sqlbuild-orchestration/src/authorization/_helpers/mod.rs +++ b/crates/sqlbuild-orchestration/src/authorization/_helpers/mod.rs @@ -3,3 +3,5 @@ pub(crate) mod administrator_roles; pub(crate) mod administrators; pub(crate) mod custom_roles; pub(crate) mod person_grants; +pub(crate) mod principal_names; +pub(crate) mod role_overview; diff --git a/crates/sqlbuild-orchestration/src/authorization/_helpers/person_grants.rs b/crates/sqlbuild-orchestration/src/authorization/_helpers/person_grants.rs index a6d05a8f58..00a01ffbb9 100644 --- a/crates/sqlbuild-orchestration/src/authorization/_helpers/person_grants.rs +++ b/crates/sqlbuild-orchestration/src/authorization/_helpers/person_grants.rs @@ -12,7 +12,7 @@ use crate::store::main::query::query; use crate::store::models::{ControlTransaction, Parameter}; use crate::store::types::Row; -const ACTIONS: [Action; 14] = [ +const ACTIONS: [Action; 16] = [ Action::View, Action::ViewSql, Action::ViewEvidence, @@ -20,6 +20,8 @@ const ACTIONS: [Action; 14] = [ Action::RunCancel, Action::RunRerun, Action::RunRewriteHistory, + Action::RunPrioritise, + Action::RunWarehouse, Action::TriggerToggle, Action::SensorResetCursor, Action::RunAdhoc, @@ -58,6 +60,14 @@ fn role_allows(role: BuiltinRole, action: Action) -> bool { } } +/// A built-in role's actions on every node, in vocabulary order. +pub(crate) fn builtin_actions(role: BuiltinRole) -> Vec { + ACTIONS + .into_iter() + .filter(|action| role_allows(role, *action)) + .collect() +} + /// The assigned and group roles of an active person for `target`; `None` when the person is disabled or unknown. pub(crate) async fn person_role_rows( transaction: ControlTransaction, diff --git a/crates/sqlbuild-orchestration/src/authorization/_helpers/principal_names.rs b/crates/sqlbuild-orchestration/src/authorization/_helpers/principal_names.rs new file mode 100644 index 0000000000..5e6605f309 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/authorization/_helpers/principal_names.rs @@ -0,0 +1,29 @@ +//! Audit actors name principals by ID; people read them by name. + +use std::collections::BTreeMap; + +use crate::store::errors::ControlError; +use crate::store::main::query::query; +use crate::store::models::ControlTransaction; + +/// Each principal's name, keyed by its audit actor text, `principal:`. +pub(crate) async fn principal_names( + transaction: ControlTransaction, +) -> Result<(ControlTransaction, BTreeMap), ControlError> { + let (transaction, rows) = query( + transaction, + "SELECT principal_id, name FROM sqb_principals", + Vec::new(), + ) + .await?; + let names: BTreeMap = rows + .iter() + .filter_map(|row| { + Some(( + format!("principal:{}", row["principal_id"].as_str()?), + row["name"].as_str()?.to_owned(), + )) + }) + .collect(); + Ok((transaction, names)) +} diff --git a/crates/sqlbuild-orchestration/src/authorization/_helpers/role_overview.rs b/crates/sqlbuild-orchestration/src/authorization/_helpers/role_overview.rs new file mode 100644 index 0000000000..dfd563abf1 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/authorization/_helpers/role_overview.rs @@ -0,0 +1,124 @@ +//! The facts behind a target's role overview, read in the caller's authorized transaction. + +use std::collections::BTreeMap; + +use uuid::Uuid; + +use crate::authorization::_helpers::custom_roles::{decode_grants, decode_roles_descriptions}; +use crate::authorization::_helpers::person_grants::builtin_actions; +use crate::authorization::models::{CurrentSnapshotId, RoleFacts, RoleHolderCount}; +use crate::credentials::models::{BuiltinRole, RoleGrant, RoleOverview, RoleOverviewGrant}; +use crate::snapshots::main::read_snapshot_documents::read_snapshot_documents; +use crate::store::errors::ControlError; +use crate::store::main::{decode_row::decode_row, query::query}; +use crate::store::models::{ControlTransaction, Parameter}; + +const BUILTIN: [(BuiltinRole, &str); 4] = [ + (BuiltinRole::Viewer, "viewer"), + (BuiltinRole::Operator, "operator"), + (BuiltinRole::Developer, "developer"), + (BuiltinRole::Admin, "admin"), +]; + +/// Published custom roles, active holders per role key, and the current snapshot's role descriptions. +pub(crate) async fn read_role_facts( + transaction: ControlTransaction, + (location_id, target): (Uuid, &str), +) -> Result<(ControlTransaction, RoleFacts), ControlError> { + let scope = vec![Parameter::Id(location_id), Parameter::Text(target.into())]; + let (transaction, custom) = query( + transaction, + "SELECT role_key, resolved_grants_json FROM sqb_effective_roles \ + WHERE location_id=$1 AND target=$2 ORDER BY role_key", + scope.clone(), + ) + .await?; + let (transaction, holders) = query( + transaction, + "SELECT h.role_key, COUNT(DISTINCT h.principal_id) AS holders FROM (\ + SELECT a.principal_id, a.role_key FROM sqb_role_assignments a \ + WHERE (a.location_id IS NULL OR a.location_id=$1) AND (a.target IS NULL OR a.target=$2) \ + UNION ALL \ + SELECT m.principal_id, g.role_key FROM sqb_group_roles g \ + JOIN sqb_person_groups m ON m.group_name=g.group_name \ + WHERE (g.location_id IS NULL OR g.location_id=$1) AND (g.target IS NULL OR g.target=$2)\ + ) h JOIN sqb_principals p ON p.principal_id=h.principal_id WHERE p.disabled_at IS NULL \ + GROUP BY h.role_key", + scope.clone(), + ) + .await?; + let (transaction, current) = query( + transaction, + "SELECT snapshot_id FROM sqb_current_snapshots WHERE location_id=$1 AND target=$2", + scope, + ) + .await?; + let current: Option = + current.into_iter().next().map(decode_row).transpose()?; + let (transaction, documents) = match current { + Some(current) => { + read_snapshot_documents(transaction, current.snapshot_id, Some(&["roles"])).await? + } + None => (transaction, BTreeMap::new()), + }; + let holders: Vec = holders + .into_iter() + .map(decode_row) + .collect::>()?; + Ok(( + transaction, + RoleFacts { + custom, + holders: holders + .into_iter() + .map(|count| (count.role_key, count.holders)) + .collect(), + descriptions: decode_roles_descriptions(documents.get("roles"))?, + }, + )) +} + +/// Built-in roles first, then custom roles by key. +pub(crate) fn role_overviews(facts: RoleFacts) -> Result, ControlError> { + let holders_of = |key: &str| facts.holders.get(key).copied().unwrap_or_default(); + let mut roles: Vec = BUILTIN + .into_iter() + .map(|(role, key)| RoleOverview { + key: key.into(), + builtin: true, + description: None, + grants: vec![RoleOverviewGrant { + nodes: Vec::new(), + permissions: builtin_actions(role), + matched: BTreeMap::new(), + }], + holders: holders_of(key), + }) + .collect(); + for row in &facts.custom { + let key = row["role_key"].as_str().unwrap_or_default().to_owned(); + roles.push(RoleOverview { + holders: holders_of(&key), + description: facts.descriptions.get(&key).cloned(), + builtin: false, + grants: decode_grants(&row["resolved_grants_json"])? + .into_iter() + .map(overview_grant) + .collect(), + key, + }); + } + Ok(roles) +} + +fn overview_grant(grant: RoleGrant) -> RoleOverviewGrant { + let mut matched: BTreeMap> = BTreeMap::new(); + for (selector, nodes) in grant.resolved_nodes { + matched.insert(selector, nodes.into_iter().collect()); + } + RoleOverviewGrant { + nodes: grant.nodes.into_iter().collect(), + permissions: grant.permissions.into_iter().collect(), + matched, + } +} diff --git a/crates/sqlbuild-orchestration/src/authorization/main/decide_person_scopes.rs b/crates/sqlbuild-orchestration/src/authorization/main/decide_person_scopes.rs new file mode 100644 index 0000000000..b080b05001 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/authorization/main/decide_person_scopes.rs @@ -0,0 +1,32 @@ +//! Decide scopes for a person without recording anything, for dry runs that must not audit while people type. + +use uuid::Uuid; + +use crate::authorization::_helpers::custom_roles::denial_reason; +use crate::authorization::_helpers::person_grants::{ + GrantDecision, decide_grant, person_role_rows, +}; +use crate::credentials::models::{DefaultRole, PermissionScope}; +use crate::store::errors::ControlError; +use crate::store::models::ControlTransaction; + +/// Each scope's refusal as a launch would show it, `None` where granted; `None` overall for an inactive person. +pub(crate) async fn decide_person_scopes( + transaction: ControlTransaction, + principal_id: Uuid, + (target, scopes): (&str, &[PermissionScope]), +) -> Result<(ControlTransaction, Option>>), ControlError> { + let (transaction, rows) = + person_role_rows(transaction, principal_id, Some(target.into())).await?; + let Some(rows) = rows else { + return Ok((transaction, None)); + }; + let mut refusals: Vec> = Vec::new(); + for scope in scopes { + refusals.push(match decide_grant(&rows, scope, DefaultRole::None) { + GrantDecision::Granted(_) => None, + GrantDecision::Denied(missing) => Some(denial_reason(scope.action, &missing)?.1), + }); + } + Ok((transaction, Some(refusals))) +} diff --git a/crates/sqlbuild-orchestration/src/authorization/main/list_role_definitions.rs b/crates/sqlbuild-orchestration/src/authorization/main/list_role_definitions.rs new file mode 100644 index 0000000000..a84bfa99b8 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/authorization/main/list_role_definitions.rs @@ -0,0 +1,30 @@ +//! Administrators who assign roles see what each role usable on a target grants and how many hold it. + +use uuid::Uuid; + +use crate::authorization::_helpers::role_overview::{read_role_facts, role_overviews}; +use crate::authorization::main::begin_authorized::begin_authorized; +use crate::credentials::models::{ + Action, AuditContext, DefaultRole, PermissionScope, RoleOverview, +}; +use crate::store::errors::ControlError; +use crate::store::main::commit::commit; +use crate::store::models::ControlStore; + +/// Built-in roles first, then custom roles by key; requires `role.assign` everywhere. +pub async fn list_role_definitions( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), + (location_id, target): (Uuid, &str), +) -> Result, ControlError> { + let (transaction, _) = begin_authorized( + store, + (context, credentials), + &PermissionScope::action(Action::RoleAssign, None), + DefaultRole::None, + ) + .await?; + let (transaction, facts) = read_role_facts(transaction, (location_id, target)).await?; + commit(transaction).await?; + role_overviews(facts) +} diff --git a/crates/sqlbuild-orchestration/src/authorization/main/list_role_holdings.rs b/crates/sqlbuild-orchestration/src/authorization/main/list_role_holdings.rs new file mode 100644 index 0000000000..28207113a2 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/authorization/main/list_role_holdings.rs @@ -0,0 +1,51 @@ +//! Administrators who assign roles see every assignment and SSO group mapping. + +use uuid::Uuid; + +use crate::authorization::main::begin_authorized::begin_authorized; +use crate::credentials::models::{ + Action, AuditContext, DefaultRole, PermissionScope, RoleHoldings, +}; +use crate::store::errors::ControlError; +use crate::store::main::{commit::commit, decode_row::decode_row, query::query}; +use crate::store::models::ControlStore; + +/// Assignments by person name, then group mappings by group; requires `role.assign` everywhere. +pub async fn list_role_holdings( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), +) -> Result { + let (transaction, _) = begin_authorized( + store, + (context, credentials), + &PermissionScope::action(Action::RoleAssign, None), + DefaultRole::None, + ) + .await?; + let (transaction, assignments) = query( + transaction, + "SELECT a.assignment_id, a.principal_id, p.name, a.role_key, a.location_id, a.target \ + FROM sqb_role_assignments a JOIN sqb_principals p ON p.principal_id=a.principal_id \ + ORDER BY p.name, a.role_key, a.location_id, a.target", + Vec::new(), + ) + .await?; + let (transaction, group_roles) = query( + transaction, + "SELECT group_name, role_key, location_id, target FROM sqb_group_roles \ + ORDER BY group_name, role_key, location_id, target", + Vec::new(), + ) + .await?; + commit(transaction).await?; + Ok(RoleHoldings { + assignments: assignments + .into_iter() + .map(decode_row) + .collect::>()?, + group_roles: group_roles + .into_iter() + .map(decode_row) + .collect::>()?, + }) +} diff --git a/crates/sqlbuild-orchestration/src/authorization/main/mod.rs b/crates/sqlbuild-orchestration/src/authorization/main/mod.rs index 03b7ff29e1..6237fd40a6 100644 --- a/crates/sqlbuild-orchestration/src/authorization/main/mod.rs +++ b/crates/sqlbuild-orchestration/src/authorization/main/mod.rs @@ -5,13 +5,15 @@ pub(crate) mod begin_authorized; pub mod bootstrap_user; pub mod change_administrator; pub mod compile_roles; +pub(crate) mod decide_person_scopes; pub(crate) mod deny_authorization; pub mod grant_admin; +pub mod list_role_definitions; +pub mod list_role_holdings; pub(crate) mod person_assigns_roles; pub(crate) mod person_lists_target; pub(crate) mod publish_custom_roles; pub(crate) mod publish_group_roles; -pub(crate) mod read_audit_events; pub(crate) mod read_person_access; pub mod read_security_audit; pub(crate) mod refuse_unknown; diff --git a/crates/sqlbuild-orchestration/src/authorization/main/read_security_audit.rs b/crates/sqlbuild-orchestration/src/authorization/main/read_security_audit.rs index 81df5bd0ff..2d6b927695 100644 --- a/crates/sqlbuild-orchestration/src/authorization/main/read_security_audit.rs +++ b/crates/sqlbuild-orchestration/src/authorization/main/read_security_audit.rs @@ -2,8 +2,9 @@ use uuid::Uuid; +use crate::authorization::_helpers::principal_names::principal_names; use crate::authorization::main::begin_authorized::begin_authorized; -use crate::authorization::main::read_audit_events::read_audit_events; +use crate::credentials::main::audit::read_audit_events::read_audit_events; use crate::credentials::models::{ Action, AuditContext, DefaultRole, PermissionScope, SecurityAuditEvent, }; @@ -14,7 +15,7 @@ use crate::store::models::ControlStore; pub async fn read_security_audit( store: &ControlStore, (context, credentials): (&AuditContext, Uuid), - (after, limit): (i64, u32), + (cursor, limit): ((i64, Option), u32), ) -> Result, ControlError> { let (transaction, _) = begin_authorized( store, @@ -23,7 +24,11 @@ pub async fn read_security_audit( DefaultRole::None, ) .await?; - let (transaction, events) = read_audit_events(transaction, (after, limit)).await?; + let (transaction, mut events) = read_audit_events(transaction, (cursor, limit)).await?; + let (transaction, names) = principal_names(transaction).await?; commit(transaction).await?; + for event in &mut events { + event.actor_name = names.get(&event.actor).cloned(); + } Ok(events) } diff --git a/crates/sqlbuild-orchestration/src/authorization/models.rs b/crates/sqlbuild-orchestration/src/authorization/models.rs index 2b2aeb7017..5e80963f3d 100644 --- a/crates/sqlbuild-orchestration/src/authorization/models.rs +++ b/crates/sqlbuild-orchestration/src/authorization/models.rs @@ -22,3 +22,22 @@ pub struct PersonAccess { pub actions: Vec, pub locations: Vec, } + +/// What a target's role overview is built from, read in one transaction. +#[derive(Debug)] +pub(crate) struct RoleFacts { + pub(crate) custom: Vec, + pub(crate) holders: std::collections::BTreeMap, + pub(crate) descriptions: std::collections::BTreeMap, +} + +#[derive(Debug, serde::Deserialize)] +pub(crate) struct RoleHolderCount { + pub(crate) role_key: String, + pub(crate) holders: u64, +} + +#[derive(Debug, serde::Deserialize)] +pub(crate) struct CurrentSnapshotId { + pub(crate) snapshot_id: Uuid, +} diff --git a/crates/sqlbuild-orchestration/src/credentials/main/audit/mod.rs b/crates/sqlbuild-orchestration/src/credentials/main/audit/mod.rs index b3e78f125f..edd3c12bcc 100644 --- a/crates/sqlbuild-orchestration/src/credentials/main/audit/mod.rs +++ b/crates/sqlbuild-orchestration/src/credentials/main/audit/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod append_audit; pub(crate) mod prune_audit; +pub(crate) mod read_audit_events; pub mod reserve_authentication_attempt; diff --git a/crates/sqlbuild-orchestration/src/authorization/main/read_audit_events.rs b/crates/sqlbuild-orchestration/src/credentials/main/audit/read_audit_events.rs similarity index 56% rename from crates/sqlbuild-orchestration/src/authorization/main/read_audit_events.rs rename to crates/sqlbuild-orchestration/src/credentials/main/audit/read_audit_events.rs index 8791f4349e..1d1281a142 100644 --- a/crates/sqlbuild-orchestration/src/authorization/main/read_audit_events.rs +++ b/crates/sqlbuild-orchestration/src/credentials/main/audit/read_audit_events.rs @@ -7,17 +7,26 @@ use crate::store::models::{ControlTransaction, Parameter}; pub(crate) async fn read_audit_events( transaction: ControlTransaction, - (after, limit): (i64, u32), + ((after, before), limit): ((i64, Option), u32), ) -> Result<(ControlTransaction, Vec), ControlError> { if after < 0 || !(1..=100).contains(&limit) { return Err(ControlError::Invalid( "audit pages require a nonnegative storage cursor and a limit between 1 and 100".into(), )); } + let (window, order) = match before { + Some(_) => ("storage_id>$1 AND storage_id<$3", "DESC"), + None => ("storage_id>$1", "ASC"), + }; + let mut parameters = vec![ + Parameter::Integer(after), + Parameter::Integer(i64::from(limit)), + ]; + parameters.extend(before.map(Parameter::Integer)); let (transaction, rows) = query(transaction, - "SELECT storage_id,event_id,occurred_at,actor,action,granting_role,request_id,ip_address,scope_json,before_json,after_json \ - FROM sqb_security_audit WHERE storage_id>$1 ORDER BY storage_id LIMIT $2", - vec![Parameter::Integer(after), Parameter::Integer(i64::from(limit))]).await?; + &format!("SELECT storage_id,event_id,occurred_at,actor,action,granting_role,request_id,ip_address,scope_json,before_json,after_json \ + FROM sqb_security_audit WHERE {window} ORDER BY storage_id {order} LIMIT $2"), + parameters).await?; Ok(( transaction, rows.into_iter().map(decode_row).collect::>()?, diff --git a/crates/sqlbuild-orchestration/src/credentials/main/identities/list_people.rs b/crates/sqlbuild-orchestration/src/credentials/main/identities/list_people.rs new file mode 100644 index 0000000000..de67a734f9 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/credentials/main/identities/list_people.rs @@ -0,0 +1,72 @@ +//! Administrators who manage people list every principal with sign-in facts, never secrets. + +use std::collections::BTreeMap; + +use chrono::{DateTime, Utc}; +use serde_json::json; +use uuid::Uuid; + +use crate::authorization::main::begin_authorized::begin_authorized; +use crate::credentials::models::{ + Action, AuditContext, DefaultRole, PermissionScope, PersonSummary, +}; +use crate::store::errors::ControlError; +use crate::store::main::{commit::commit, decode_row::decode_row, query::query}; +use crate::store::models::{ControlStore, Parameter}; +use crate::store::types::Row; + +/// Everyone by name, with sessions still live at `now`; requires `user.manage` everywhere. +pub async fn list_people( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), + now: DateTime, +) -> Result, ControlError> { + let (transaction, _) = begin_authorized( + store, + (context, credentials), + &PermissionScope::action(Action::UserManage, None), + DefaultRole::None, + ) + .await?; + let (transaction, people) = query(transaction, + "SELECT p.principal_id, p.name, p.created_at, p.disabled_at, \ + CASE WHEN c.principal_id IS NULL THEN 0 ELSE 1 END AS has_password, \ + (SELECT COUNT(*) FROM sqb_sessions s WHERE s.principal_id=p.principal_id \ + AND s.revoked_at IS NULL AND s.expires_at>$1 AND p.disabled_at IS NULL) AS active_sessions, \ + (SELECT MAX(s.created_at) FROM sqb_sessions s WHERE s.principal_id=p.principal_id) AS last_signed_in_at \ + FROM sqb_principals p LEFT JOIN sqb_password_credentials c ON c.principal_id=p.principal_id ORDER BY p.name", + vec![Parameter::Time(now)]).await?; + let (transaction, memberships) = query( + transaction, + "SELECT principal_id, group_name FROM sqb_person_groups ORDER BY group_name", + Vec::new(), + ) + .await?; + commit(transaction).await?; + let mut groups: BTreeMap> = BTreeMap::new(); + for row in &memberships { + if let (Some(id), Some(group)) = (row["principal_id"].as_str(), row["group_name"].as_str()) + { + groups.entry(id.into()).or_default().push(group.into()); + } + } + people + .into_iter() + .map(|row| person_of(row, &groups)) + .collect() +} + +fn person_of( + mut row: Row, + groups: &BTreeMap>, +) -> Result { + let member_of = row["principal_id"] + .as_str() + .and_then(|id| groups.get(id)) + .cloned() + .unwrap_or_default(); + let has_password = row["has_password"].as_i64() == Some(1); + row.insert("groups".into(), json!(member_of)); + row.insert("has_password".into(), json!(has_password)); + decode_row(row) +} diff --git a/crates/sqlbuild-orchestration/src/credentials/main/identities/mod.rs b/crates/sqlbuild-orchestration/src/credentials/main/identities/mod.rs index 54451634e7..59b28fe23a 100644 --- a/crates/sqlbuild-orchestration/src/credentials/main/identities/mod.rs +++ b/crates/sqlbuild-orchestration/src/credentials/main/identities/mod.rs @@ -1,2 +1,3 @@ pub mod create_principal; pub(crate) mod insert_principal; +pub mod list_people; diff --git a/crates/sqlbuild-orchestration/src/credentials/models.rs b/crates/sqlbuild-orchestration/src/credentials/models.rs index fc0a74344e..f2027c65c2 100644 --- a/crates/sqlbuild-orchestration/src/credentials/models.rs +++ b/crates/sqlbuild-orchestration/src/credentials/models.rs @@ -39,6 +39,72 @@ pub struct SecurityAuditEvent { pub scope_json: serde_json::Value, pub before_json: serde_json::Value, pub after_json: serde_json::Value, + /// The person's name when `actor` is a principal; person reads set it, exports leave it out. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub actor_name: Option, +} + +/// A person as administrators see them: how they sign in, their SSO groups and live sessions. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct PersonSummary { + pub principal_id: Uuid, + pub name: String, + pub created_at: DateTime, + pub disabled_at: Option>, + pub has_password: bool, + /// Groups the identity provider reported at the person's last SSO sign-in. + pub groups: Vec, + /// Unrevoked, unexpired browser and CLI sessions; none while the person is disabled. + pub active_sessions: i64, + pub last_signed_in_at: Option>, +} + +/// Who holds which role where: assigned to people here, or mapped to SSO groups in server config. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RoleHoldings { + pub assignments: Vec, + pub group_roles: Vec, +} + +/// A role usable on one location target: built in, or published by the location's project. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RoleOverview { + pub key: String, + pub builtin: bool, + /// The project's description; absent for built-in roles. + pub description: Option, + /// Built-in roles: one grant without selectors, meaning every node. Custom roles: their published grants. + pub grants: Vec, + /// Active people holding the role here or everywhere, assigned directly or through an SSO group. + pub holders: u64, +} + +/// Node selectors, the actions they grant, and the nodes each selector matched when published. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RoleOverviewGrant { + pub nodes: Vec, + pub permissions: Vec, + pub matched: std::collections::BTreeMap>, +} + +/// A role assigned to one person, everywhere or on one location and target. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct PersonRole { + pub assignment_id: Uuid, + pub principal_id: Uuid, + pub name: String, + pub role_key: String, + pub location_id: Option, + pub target: Option, +} + +/// A role every member of an SSO group holds. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct GroupRole { + pub group_name: String, + pub role_key: String, + pub location_id: Option, + pub target: Option, } /// Installation configuration: rows past the horizon stay until every sink and retry lookup is done. @@ -89,6 +155,10 @@ pub enum Action { RunRerun, #[serde(rename = "run.rewrite_history")] RunRewriteHistory, + #[serde(rename = "run.prioritise")] + RunPrioritise, + #[serde(rename = "run.warehouse")] + RunWarehouse, #[serde(rename = "trigger.toggle")] TriggerToggle, #[serde(rename = "sensor.reset_cursor")] diff --git a/crates/sqlbuild-orchestration/src/declarations/_helpers/reads.rs b/crates/sqlbuild-orchestration/src/declarations/_helpers/reads.rs index 00f3bf5150..ac716e189b 100644 --- a/crates/sqlbuild-orchestration/src/declarations/_helpers/reads.rs +++ b/crates/sqlbuild-orchestration/src/declarations/_helpers/reads.rs @@ -3,6 +3,7 @@ use uuid::Uuid; use crate::declarations::models::{JobRecord, StoredJob, TriggerRecord}; +use crate::scheduler::models::TickRecord; use crate::store::errors::ControlError; use crate::store::main::query_records::query_records; use crate::store::models::{ControlTransaction, Parameter}; @@ -42,3 +43,16 @@ pub(crate) async fn jobs_in( .collect(); Ok((transaction, jobs)) } + +/// The newest tick of every published trigger in the target, each found through the trigger-time index. +pub(crate) async fn latest_ticks_in( + transaction: ControlTransaction, + (location_id, target): (Uuid, &str), +) -> Result<(ControlTransaction, Vec), ControlError> { + query_records(transaction, + "SELECT k.tick_id, k.trigger_id, k.scheduled_for, k.evaluated_at, k.epoch, k.outcome, k.run_id, k.covered_by_run_id \ + FROM sqb_triggers t JOIN sqb_ticks k ON k.tick_id=(SELECT x.tick_id FROM sqb_ticks x WHERE x.trigger_id=t.trigger_id \ + ORDER BY x.scheduled_for DESC LIMIT 1) \ + WHERE t.location_id=$1 AND t.target=$2 AND t.removed_in_snapshot_id IS NULL", + vec![Parameter::Id(location_id), Parameter::Text(target.into())]).await +} diff --git a/crates/sqlbuild-orchestration/src/declarations/main/list_person_triggers.rs b/crates/sqlbuild-orchestration/src/declarations/main/list_person_triggers.rs index 472327f34c..3e178777ac 100644 --- a/crates/sqlbuild-orchestration/src/declarations/main/list_person_triggers.rs +++ b/crates/sqlbuild-orchestration/src/declarations/main/list_person_triggers.rs @@ -1,11 +1,13 @@ //! A person lists triggers only with view on every current node of the target. +use std::collections::BTreeMap; + use uuid::Uuid; use crate::authorization::main::authorize_listing::authorize_listing; use crate::credentials::models::{AuditContext, DefaultRole}; -use crate::declarations::_helpers::reads::triggers_in; -use crate::declarations::models::TriggerRecord; +use crate::declarations::_helpers::reads::{latest_ticks_in, triggers_in}; +use crate::declarations::models::PersonTriggerRecord; use crate::store::errors::ControlError; use crate::store::main::{begin::begin, commit::commit}; use crate::store::models::{ControlStore, TransactionMode}; @@ -15,7 +17,7 @@ pub async fn list_person_triggers( (context, credentials): (&AuditContext, Uuid), (location_id, target): (Uuid, &str), default_role: DefaultRole, -) -> Result, ControlError> { +) -> Result, ControlError> { let (transaction, _) = authorize_listing( begin(store, TransactionMode::Write).await?, (context, credentials), @@ -24,6 +26,17 @@ pub async fn list_person_triggers( ) .await?; let (transaction, triggers) = triggers_in(transaction, (location_id, target)).await?; + let (transaction, ticks) = latest_ticks_in(transaction, (location_id, target)).await?; commit(transaction).await?; - Ok(triggers) + let mut latest: BTreeMap = ticks + .into_iter() + .map(|tick| (tick.trigger_id, tick)) + .collect(); + Ok(triggers + .into_iter() + .map(|trigger| PersonTriggerRecord { + latest_tick: latest.remove(&trigger.trigger_id), + trigger, + }) + .collect()) } diff --git a/crates/sqlbuild-orchestration/src/declarations/models.rs b/crates/sqlbuild-orchestration/src/declarations/models.rs index 726975e856..8a981a57b4 100644 --- a/crates/sqlbuild-orchestration/src/declarations/models.rs +++ b/crates/sqlbuild-orchestration/src/declarations/models.rs @@ -119,6 +119,14 @@ pub struct TriggerRecord { pub next_due_at: Option>, } +/// A trigger as people list it, with its newest tick so a list needs no read per trigger. +#[derive(Clone, Debug, PartialEq, Serialize, utoipa::ToSchema)] +pub struct PersonTriggerRecord { + #[serde(flatten)] + pub trigger: TriggerRecord, + pub latest_tick: Option, +} + /// Operator changes are immutable facts; clearing an override restores the published code default. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] #[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)] diff --git a/crates/sqlbuild-orchestration/src/platform/_helpers/mod.rs b/crates/sqlbuild-orchestration/src/platform/_helpers/mod.rs index 0c2b80f5ca..a302fe0aee 100644 --- a/crates/sqlbuild-orchestration/src/platform/_helpers/mod.rs +++ b/crates/sqlbuild-orchestration/src/platform/_helpers/mod.rs @@ -2,6 +2,8 @@ pub(crate) mod connection_tests; pub(crate) mod liveness; pub(crate) mod location_heartbeat; pub(crate) mod observations; +pub(crate) mod problems; +pub(crate) mod recent_health; pub(crate) mod retention; pub(crate) mod watchdog; pub(crate) mod work_client; diff --git a/crates/sqlbuild-orchestration/src/platform/_helpers/problems.rs b/crates/sqlbuild-orchestration/src/platform/_helpers/problems.rs new file mode 100644 index 0000000000..f8e57a9574 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/platform/_helpers/problems.rs @@ -0,0 +1,97 @@ +//! Live problem queries, with the watchdog's thresholds; callers decide who may see each target's. + +use chrono::{DateTime, TimeDelta, Utc}; +use uuid::Uuid; + +use crate::platform::models::{LateTarget, OverdueTick, ProblemScan}; +use crate::store::errors::ControlError; +use crate::store::main::decode_row::decode_row; +use crate::store::main::query::query; +use crate::store::main::{begin::begin, commit::commit}; +use crate::store::models::{ControlStore, Parameter, TransactionMode}; + +pub(crate) async fn scan_problems(store: &ControlStore) -> Result { + let transaction = begin(store, TransactionMode::Read).await?; + let now = transaction.now; + let (transaction, leases) = query( + transaction, + "SELECT expires_at FROM sqb_scheduler_lease WHERE name='scheduler'", + vec![], + ) + .await?; + let (transaction, locations) = query( + transaction, + "SELECT i.location_id, l.name AS location_name, i.target, MAX(i.last_seen_at) AS last_seen_at \ + FROM sqb_location_instances i JOIN sqb_locations l ON l.location_id=i.location_id \ + WHERE l.archived_at IS NULL GROUP BY i.location_id,l.name,i.target \ + HAVING MAX(i.last_seen_at)<$1 ORDER BY i.location_id,i.target", + vec![Parameter::Time(now - TimeDelta::seconds(90))], + ) + .await?; + let (transaction, late_targets) = query( + transaction, + "SELECT DISTINCT t.location_id, t.target FROM sqb_triggers t \ + JOIN sqb_trigger_state s ON s.trigger_id=t.trigger_id \ + JOIN sqb_locations l ON l.location_id=t.location_id \ + WHERE t.kind='schedule' AND s.status='running' AND t.removed_in_snapshot_id IS NULL \ + AND l.archived_at IS NULL AND s.next_due_at<$1", + vec![Parameter::Time(overdue_before(now))], + ) + .await?; + commit(transaction).await?; + Ok(ProblemScan { + now, + lease: leases.into_iter().next().map(decode_row).transpose()?, + broken: locations + .into_iter() + .map(decode_row) + .collect::>()?, + late_targets: late_targets + .into_iter() + .map(|row| { + let target: LateTarget = decode_row(row)?; + Ok((target.location_id, target.target)) + }) + .collect::>()?, + }) +} + +/// At most 100 late ticks per target, oldest first. +pub(crate) async fn late_ticks( + store: &ControlStore, + targets: impl IntoIterator, + now: DateTime, +) -> Result, ControlError> { + let mut overdue: Vec = Vec::new(); + let mut transaction = begin(store, TransactionMode::Read).await?; + for (location_id, target) in targets { + let (next, ticks) = query( + transaction, + "SELECT t.trigger_id, t.name, t.location_id, l.name AS location_name, t.target, \ + s.next_due_at AS scheduled_for FROM sqb_triggers t \ + JOIN sqb_trigger_state s ON s.trigger_id=t.trigger_id \ + JOIN sqb_locations l ON l.location_id=t.location_id WHERE t.location_id=$1 AND t.target=$2 \ + AND t.kind='schedule' AND s.status='running' AND t.removed_in_snapshot_id IS NULL \ + AND s.next_due_at<$3 ORDER BY s.next_due_at, t.name LIMIT 100", + vec![ + Parameter::Id(*location_id), + Parameter::Text(target.clone()), + Parameter::Time(overdue_before(now)), + ], + ) + .await?; + transaction = next; + overdue.extend( + ticks + .into_iter() + .map(decode_row) + .collect::, _>>()?, + ); + } + commit(transaction).await?; + Ok(overdue) +} + +fn overdue_before(now: DateTime) -> DateTime { + now - TimeDelta::seconds(30) +} diff --git a/crates/sqlbuild-orchestration/src/platform/_helpers/recent_health.rs b/crates/sqlbuild-orchestration/src/platform/_helpers/recent_health.rs new file mode 100644 index 0000000000..40e68baa26 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/platform/_helpers/recent_health.rs @@ -0,0 +1,76 @@ +//! The newest health observations of a location: reported events and instance liveness rows. + +use uuid::Uuid; + +use crate::platform::models::LocationHealthObservation; +use crate::store::errors::ControlError; +use crate::store::main::decode_row::decode_row; +use crate::store::main::query::query; +use crate::store::models::{ControlTransaction, Parameter}; + +/// At most 100, newest first, from today's and yesterday's reports plus each instance's last check-in, of one target when given. +pub(crate) async fn recent_health( + transaction: ControlTransaction, + location_id: Uuid, + target: Option<&str>, +) -> Result<(ControlTransaction, Vec), ControlError> { + let today = transaction.now.date_naive(); + let yesterday = today - chrono::TimeDelta::days(1); + let (event_target, instance_target) = match target { + Some(_) => (" AND payload->'report'->>'target'=$3", " AND target=$2"), + None => ("", ""), + }; + let target_parameter: Vec = target + .map(|target| Parameter::Text(target.to_owned())) + .into_iter() + .collect(); + let (transaction, rows) = query( + transaction, + &format!("SELECT received_at, payload FROM (\ + SELECT * FROM (SELECT occurred_at AS received_at, invocation_sequence, payload FROM recall_events \ + WHERE invocation_id=$1 AND event_type='location_health'{event_target} ORDER BY invocation_sequence DESC LIMIT 100) current_day \ + UNION ALL \ + SELECT * FROM (SELECT occurred_at AS received_at, invocation_sequence, payload FROM recall_events \ + WHERE invocation_id=$2 AND event_type='location_health'{event_target} ORDER BY invocation_sequence DESC LIMIT 100) previous_day\ + ) observations ORDER BY received_at DESC, invocation_sequence DESC LIMIT 100"), + [ + Parameter::Text(format!("location/{location_id}/{today}")), + Parameter::Text(format!("location/{location_id}/{yesterday}")), + ] + .into_iter() + .chain(target_parameter.clone()) + .collect(), + ) + .await?; + let mut observations: Vec = rows + .into_iter() + .map(|mut row| { + let report = row + .remove("payload") + .and_then(|mut payload| payload.get_mut("report").map(serde_json::Value::take)) + .ok_or_else(|| ControlError::Invalid("location health report is missing".into()))?; + row.insert("report".into(), report); + decode_row(row) + }) + .collect::>()?; + let (transaction, instances) = query( + transaction, + &format!( + "SELECT last_seen_at AS received_at, health_json AS report FROM sqb_location_instances \ + WHERE location_id=$1{instance_target} ORDER BY last_seen_at DESC LIMIT 100" + ), + std::iter::once(Parameter::Id(location_id)) + .chain(target_parameter) + .collect(), + ) + .await?; + observations.extend( + instances + .into_iter() + .map(decode_row) + .collect::, _>>()?, + ); + observations.sort_by_key(|observation| std::cmp::Reverse(observation.received_at)); + observations.truncate(100); + Ok((transaction, observations)) +} diff --git a/crates/sqlbuild-orchestration/src/platform/main/location_health.rs b/crates/sqlbuild-orchestration/src/platform/main/location_health.rs index 977a61e6c4..148e6fb77f 100644 --- a/crates/sqlbuild-orchestration/src/platform/main/location_health.rs +++ b/crates/sqlbuild-orchestration/src/platform/main/location_health.rs @@ -2,62 +2,22 @@ use uuid::Uuid; +use crate::platform::_helpers::recent_health::recent_health; use crate::platform::models::LocationHealthObservation; use crate::store::errors::ControlError; -use crate::store::main::begin::begin; -use crate::store::main::commit::commit; -use crate::store::main::decode_row::decode_row; -use crate::store::main::query::query; -use crate::store::models::{ControlStore, Parameter, TransactionMode}; +use crate::store::main::{begin::begin, commit::commit}; +use crate::store::models::{ControlStore, TransactionMode}; pub async fn location_health( store: &ControlStore, location_id: Uuid, ) -> Result, ControlError> { - let transaction = begin(store, TransactionMode::Read).await?; - let today = transaction.now.date_naive(); - let yesterday = today - chrono::TimeDelta::days(1); - let (transaction, rows) = query( - transaction, - "SELECT received_at, payload FROM (\ - SELECT * FROM (SELECT occurred_at AS received_at, invocation_sequence, payload FROM recall_events \ - WHERE invocation_id=$1 AND event_type='location_health' ORDER BY invocation_sequence DESC LIMIT 100) current_day \ - UNION ALL \ - SELECT * FROM (SELECT occurred_at AS received_at, invocation_sequence, payload FROM recall_events \ - WHERE invocation_id=$2 AND event_type='location_health' ORDER BY invocation_sequence DESC LIMIT 100) previous_day\ - ) observations ORDER BY received_at DESC, invocation_sequence DESC LIMIT 100", - vec![ - Parameter::Text(format!("location/{location_id}/{today}")), - Parameter::Text(format!("location/{location_id}/{yesterday}")), - ], + let (transaction, observations) = recent_health( + begin(store, TransactionMode::Read).await?, + location_id, + None, ) .await?; - let mut observations: Vec = rows - .into_iter() - .map(|mut row| { - let report = row - .remove("payload") - .and_then(|mut payload| payload.get_mut("report").map(serde_json::Value::take)) - .ok_or_else(|| ControlError::Invalid("location health report is missing".into()))?; - row.insert("report".into(), report); - decode_row(row) - }) - .collect::>()?; - let (transaction, instances) = query( - transaction, - "SELECT last_seen_at AS received_at, health_json AS report FROM sqb_location_instances \ - WHERE location_id=$1 ORDER BY last_seen_at DESC LIMIT 100", - vec![Parameter::Id(location_id)], - ) - .await?; - observations.extend( - instances - .into_iter() - .map(decode_row) - .collect::, _>>()?, - ); - observations.sort_by_key(|observation| std::cmp::Reverse(observation.received_at)); - observations.truncate(100); commit(transaction).await?; Ok(observations) } diff --git a/crates/sqlbuild-orchestration/src/platform/main/mod.rs b/crates/sqlbuild-orchestration/src/platform/main/mod.rs index ecf74ceb6f..d1c062ba19 100644 --- a/crates/sqlbuild-orchestration/src/platform/main/mod.rs +++ b/crates/sqlbuild-orchestration/src/platform/main/mod.rs @@ -5,6 +5,8 @@ pub mod location_health; pub mod location_work; pub(crate) mod read_location_work; pub mod read_person_connection_test; +pub mod read_person_location_health; +pub mod read_person_problems; pub(crate) mod read_platform_events; pub mod report_location_health; pub mod report_remote_location_health; diff --git a/crates/sqlbuild-orchestration/src/platform/main/read_person_location_health.rs b/crates/sqlbuild-orchestration/src/platform/main/read_person_location_health.rs new file mode 100644 index 0000000000..91b9eeab0d --- /dev/null +++ b/crates/sqlbuild-orchestration/src/platform/main/read_person_location_health.rs @@ -0,0 +1,30 @@ +//! People read a target's health only with view on every current node, like its listings. + +use uuid::Uuid; + +use crate::authorization::main::authorize_listing::authorize_listing; +use crate::credentials::models::{AuditContext, DefaultRole}; +use crate::platform::_helpers::recent_health::recent_health; +use crate::platform::models::LocationHealthObservation; +use crate::store::errors::ControlError; +use crate::store::main::{begin::begin, commit::commit}; +use crate::store::models::{ControlStore, TransactionMode}; + +/// The location's newest observations for this target only, newest first. +pub async fn read_person_location_health( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), + (location_id, target): (Uuid, &str), + default_role: DefaultRole, +) -> Result, ControlError> { + let (transaction, _) = authorize_listing( + begin(store, TransactionMode::Write).await?, + (context, credentials), + (location_id, target), + default_role, + ) + .await?; + let (transaction, observations) = recent_health(transaction, location_id, Some(target)).await?; + commit(transaction).await?; + Ok(observations) +} diff --git a/crates/sqlbuild-orchestration/src/platform/main/read_person_problems.rs b/crates/sqlbuild-orchestration/src/platform/main/read_person_problems.rs new file mode 100644 index 0000000000..5644e4f32c --- /dev/null +++ b/crates/sqlbuild-orchestration/src/platform/main/read_person_problems.rs @@ -0,0 +1,46 @@ +//! People see the scheduler's state, and broken locations and late ticks only for targets they list. + +use std::collections::BTreeSet; + +use uuid::Uuid; + +use crate::authorization::main::person_lists_target::person_lists_target; +use crate::credentials::models::DefaultRole; +use crate::platform::_helpers::problems::{late_ticks, scan_problems}; +use crate::platform::models::PlatformProblems; +use crate::store::errors::ControlError; +use crate::store::models::ControlStore; + +/// Access is checked per target before that target's late ticks are read, so one target cannot crowd out another. +pub async fn read_person_problems( + store: &ControlStore, + principal_id: Uuid, + default_role: DefaultRole, +) -> Result { + let scan = scan_problems(store).await?; + let mut listed: BTreeSet<(Uuid, String)> = BTreeSet::new(); + for (location_id, target) in scan + .broken + .iter() + .map(|location| (location.location_id, location.target.clone())) + .chain(scan.late_targets.iter().cloned()) + .collect::>() + { + if person_lists_target(store, principal_id, (location_id, &target), default_role).await? { + listed.insert((location_id, target)); + } + } + Ok(PlatformProblems { + scheduler_leader: scan + .lease + .as_ref() + .is_some_and(|lease| lease.expires_at > scan.now), + scheduler_lease_expires_at: scan.lease.map(|lease| lease.expires_at), + overdue_ticks: late_ticks(store, scan.late_targets.intersection(&listed), scan.now).await?, + broken_locations: scan + .broken + .into_iter() + .filter(|location| listed.contains(&(location.location_id, location.target.clone()))) + .collect(), + }) +} diff --git a/crates/sqlbuild-orchestration/src/platform/models.rs b/crates/sqlbuild-orchestration/src/platform/models.rs index 543aaeb420..1bb5c963c8 100644 --- a/crates/sqlbuild-orchestration/src/platform/models.rs +++ b/crates/sqlbuild-orchestration/src/platform/models.rs @@ -250,3 +250,53 @@ pub struct PlatformEventEnvelope { pub struct PlatformEventPayload { pub platform: PlatformEvent, } + +/// Problems derived from live state when read; nothing here is stored. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct PlatformProblems { + pub scheduler_leader: bool, + /// When the last scheduler lease ran out, or will run out; absent until a server first leads. + pub scheduler_lease_expires_at: Option>, + pub broken_locations: Vec, + pub overdue_ticks: Vec, +} + +/// Every instance of this location target has missed two check-ins. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct BrokenLocation { + pub location_id: Uuid, + pub location_name: String, + pub target: String, + pub last_seen_at: DateTime, +} + +/// A running schedule whose next tick is more than 30 seconds late. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct OverdueTick { + pub trigger_id: Uuid, + pub name: String, + pub location_id: Uuid, + pub location_name: String, + pub target: String, + pub scheduled_for: DateTime, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct SchedulerLease { + pub(crate) expires_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct LateTarget { + pub(crate) location_id: Uuid, + pub(crate) target: String, +} + +/// Problems found at one instant: the scheduler lease, broken location targets and targets with late ticks. +#[derive(Debug)] +pub(crate) struct ProblemScan { + pub(crate) now: DateTime, + pub(crate) lease: Option, + pub(crate) broken: Vec, + pub(crate) late_targets: std::collections::BTreeSet<(Uuid, String)>, +} diff --git a/crates/sqlbuild-orchestration/src/runs/_helpers/requests/adhoc.rs b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/adhoc.rs new file mode 100644 index 0000000000..a6792c1b07 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/adhoc.rs @@ -0,0 +1,259 @@ +//! Resolve a command against the current publication and freeze it as an ad-hoc run request. + +use std::collections::BTreeSet; + +use serde_json::{Value, json}; +use uuid::Uuid; + +use crate::authorization::main::authorize_listing::authorize_listing; +use crate::authorization::main::decide_person_scopes::decide_person_scopes; +use crate::credentials::main::audit::append_audit::append_audit; +use crate::credentials::models::{Action, AuditContext, DefaultRole, PermissionScope}; +use crate::runs::_helpers::requests::permissions::{authorize_execution, execution_scopes}; +use crate::runs::_helpers::requests::publication::{ + publish_request, record_authored, request_retry, +}; +use crate::runs::constants::{ADHOC_AUDIT_TAG, LAUNCHABLE_VERBS}; +use crate::runs::models::{ + AdhocCheckRequest, AdhocPermission, AdhocRunRequest, PlannerResource, RunKind, RunOrigin, + RunRequest, +}; +use crate::runs::types::CommandPlanner; +use crate::snapshots::main::job_write_set::job_write_set; +use crate::snapshots::main::read_current_record::read_current_record; +use crate::snapshots::main::read_snapshot_nodes::read_snapshot_nodes; +use crate::snapshots::models::SnapshotNode; +use crate::store::errors::ControlError; +use crate::store::main::query::query; +use crate::store::models::{ControlTransaction, Parameter}; + +/// Graph kinds in the order the CLI builds its project graph: models, sources, seeds, then functions. +const GRAPH_KINDS: [&str; 5] = ["model", "source", "seed", "udf", "table_fn"]; + +/// The frozen request and its verb, or why the command cannot run; `run_id` comes from the caller. +pub(crate) async fn frozen_adhoc_request( + transaction: ControlTransaction, + (context, planner): (&AuditContext, &dyn CommandPlanner), + (request, run_id): (&AdhocCheckRequest, Uuid), +) -> Result<(ControlTransaction, Result<(String, RunRequest), String>), ControlError> { + let (transaction, snapshot) = + read_current_record(transaction, (request.location_id, &request.target)).await?; + let Some(snapshot) = snapshot else { + return Ok(( + transaction, + Err("this target has no publication yet".into()), + )); + }; + let (transaction, nodes) = read_snapshot_nodes(transaction, snapshot.snapshot_id, None).await?; + let plan = match planner.plan(&request.command, &planner_resources(&nodes)) { + Ok(plan) => plan, + Err(refusal) => return Ok((transaction, Err(refusal))), + }; + let (transaction, writers) = + job_write_set(transaction, snapshot.snapshot_id, &plan.selection).await?; + let (transaction, images) = query( + transaction, + "SELECT image_id FROM sqb_snapshots WHERE snapshot_id=$1", + vec![Parameter::Id(snapshot.snapshot_id)], + ) + .await?; + let image_id = images + .first() + .and_then(|row| row["image_id"].as_str()) + .map(str::to_owned); + let write_set: BTreeSet = match plan.verb.as_str() { + "build" => writers, + "seed" => writers + .into_iter() + .filter(|key| key.starts_with("seed:")) + .collect(), + _ => BTreeSet::new(), + }; + Ok(( + transaction, + Ok(( + plan.verb, + RunRequest { + run_id, + location_id: request.location_id, + target: request.target.clone(), + snapshot_id: snapshot.snapshot_id, + image_id, + kind: RunKind::Adhoc, + origin: RunOrigin::Manual, + job_key: None, + parent_run_id: None, + root_run_id: run_id, + trigger_id: None, + tick_id: None, + rerun_of_run_id: None, + requested_by: context.actor.clone(), + priority: request.priority, + selection: plan.selection, + write_set, + settings: plan.settings, + window: None, + concurrency_groups: BTreeSet::new(), + }, + )), + )) +} + +/// The selector graph rebuilt from published definitions, in the CLI's kind order. +fn planner_resources( + nodes: &std::collections::BTreeMap, +) -> Vec { + let mut resources: Vec = Vec::new(); + for kind in GRAPH_KINDS { + for (key, node) in nodes { + if node.kind != kind { + continue; + } + let definition = &node.definition; + let (tags, folder) = match kind { + "model" => ( + definition.pointer("/config/tags"), + Some(model_folder(definition)), + ), + "seed" => (definition.pointer("/schema/tags"), None), + _ => (definition.get("tags"), None), + }; + resources.push(PlannerResource { + key: key.clone(), + deps: texts(definition.get("deps")), + tags: texts(tags), + folder, + }); + } + } + resources +} + +/// The model file's folder relative to the project, with `/` separators whichever platform published it. +fn model_folder(definition: &Value) -> String { + let path = definition + .get("path") + .and_then(Value::as_str) + .unwrap_or_default() + .replace('\\', "/"); + path.rsplit_once('/') + .map_or_else(|| ".".to_owned(), |(folder, _)| folder.to_owned()) +} + +fn texts(value: Option<&Value>) -> Vec { + value + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|item| item.as_str().map(str::to_owned)) + .collect() +} + +/// Whether the person may view every current node, decided without recording a denial. +pub(in crate::runs) async fn may_view_target( + transaction: ControlTransaction, + principal_id: Uuid, + request: &AdhocCheckRequest, +) -> Result<(ControlTransaction, bool), ControlError> { + let (transaction, current) = query(transaction, + "SELECT n.node_key FROM sqb_snapshot_nodes n JOIN sqb_current_snapshots c ON c.snapshot_id=n.snapshot_id \ + WHERE c.location_id=$1 AND c.target=$2", + vec![Parameter::Id(request.location_id), Parameter::Text(request.target.clone())]).await?; + let view = PermissionScope { + action: Action::View, + location_id: Some(request.location_id), + target: Some(request.target.clone()), + nodes: current + .iter() + .filter_map(|row| row["node_key"].as_str().map(str::to_owned)) + .collect(), + }; + let (transaction, refusals) = + decide_person_scopes(transaction, principal_id, (&request.target, &[view])).await?; + Ok((transaction, refusals.as_deref() == Some(&[None]))) +} + +/// Every permission launching `frozen` needs, decided as the launch would but without recording a denial. +pub(in crate::runs) async fn permission_decisions( + transaction: ControlTransaction, + principal_id: Uuid, + frozen: &RunRequest, +) -> Result<(ControlTransaction, Vec), ControlError> { + let scopes = execution_scopes(frozen, std::slice::from_ref(frozen), Action::RunAdhoc); + let (transaction, refusals) = + decide_person_scopes(transaction, principal_id, (&frozen.target, &scopes)).await?; + let inactive = Some("authenticated active person required".to_owned()); + let refusals: Vec> = refusals.unwrap_or_else(|| vec![inactive; scopes.len()]); + Ok(( + transaction, + scopes + .iter() + .zip(refusals) + .map(|(scope, reason)| AdhocPermission { + action: scope.action, + allowed: reason.is_none(), + reason, + }) + .collect(), + )) +} + +/// Resolve, authorise on the exact resolved nodes, publish and audit; workers never re-resolve the caller's selectors. +pub(in crate::runs) async fn request_adhoc_run_in( + transaction: ControlTransaction, + (context, principal_id): (&AuditContext, Uuid), + (planner, request): (&dyn CommandPlanner, &AdhocRunRequest), +) -> Result<(ControlTransaction, bool), ControlError> { + let (transaction, context) = authorize_listing( + transaction, + (context, principal_id), + (request.location_id, &request.target), + DefaultRole::None, + ) + .await?; + if request.reason.trim().is_empty() || request.command.is_empty() { + return Err(ControlError::Invalid( + "an ad-hoc run needs a command and a reason".into(), + )); + } + let authored = json!(request); + let (transaction, retry) = + request_retry(transaction, &context, (request.run_id, &authored)).await?; + if retry { + return Ok((transaction, false)); + } + let check = AdhocCheckRequest { + location_id: request.location_id, + target: request.target.clone(), + command: request.command.clone(), + priority: request.priority, + }; + let (transaction, frozen) = + frozen_adhoc_request(transaction, (&context, planner), (&check, request.run_id)).await?; + let (verb, frozen) = frozen.map_err(ControlError::Invalid)?; + if !LAUNCHABLE_VERBS.contains(&verb.as_str()) { + return Err(ControlError::Invalid(format!( + "workers do not run sqb {verb} yet; only build can launch" + ))); + } + let (transaction, context) = authorize_execution( + transaction, + (&context, principal_id), + (&frozen, Action::RunAdhoc), + ) + .await?; + let (transaction, _) = publish_request(transaction, &frozen).await?; + let transaction = record_authored(transaction, &context, (request.run_id, &authored)).await?; + let transaction = append_audit( + transaction, + &context, + (Uuid::new_v5(&request.run_id, ADHOC_AUDIT_TAG), "run.adhoc_requested"), + ( + json!({"location_id": request.location_id, "target": request.target, "run_id": request.run_id}), + json!(null), + json!({"request": request, "verb": verb, "frozen": frozen}), + ), + ) + .await?; + Ok((transaction, true)) +} diff --git a/crates/sqlbuild-orchestration/src/runs/main/cancel_person_run.rs b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/cancel.rs similarity index 98% rename from crates/sqlbuild-orchestration/src/runs/main/cancel_person_run.rs rename to crates/sqlbuild-orchestration/src/runs/_helpers/requests/cancel.rs index 9ab7039ba1..47413fa054 100644 --- a/crates/sqlbuild-orchestration/src/runs/main/cancel_person_run.rs +++ b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/cancel.rs @@ -13,7 +13,7 @@ use crate::store::errors::ControlError; use crate::store::main::{begin::begin, commit::commit, query::query}; use crate::store::models::{ControlStore, Parameter, TransactionMode}; -pub async fn cancel_person_run( +pub(crate) async fn cancel_person_run( store: &ControlStore, credentials: (&AuditContext, Uuid), request: &CancelRunRequest, diff --git a/crates/sqlbuild-orchestration/src/runs/_helpers/requests/mod.rs b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/mod.rs index fbd365703f..2231ab538f 100644 --- a/crates/sqlbuild-orchestration/src/runs/_helpers/requests/mod.rs +++ b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/mod.rs @@ -1,3 +1,5 @@ +pub(in crate::runs) mod adhoc; +pub(in crate::runs) mod cancel; pub(in crate::runs) mod job; pub(in crate::runs) mod manual_job; pub(in crate::runs) mod manual_rerun; diff --git a/crates/sqlbuild-orchestration/src/runs/_helpers/requests/permissions.rs b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/permissions.rs index 58baf5f024..9ee5c9fd27 100644 --- a/crates/sqlbuild-orchestration/src/runs/_helpers/requests/permissions.rs +++ b/crates/sqlbuild-orchestration/src/runs/_helpers/requests/permissions.rs @@ -1,5 +1,7 @@ //! Person authority covers the effective frozen execution, including sensitive group children. +use std::collections::BTreeSet; + use uuid::Uuid; use crate::authorization::main::authorize_person::authorize_person; @@ -9,6 +11,24 @@ use crate::runs::models::{RunKind, RunRequest}; use crate::store::errors::ControlError; use crate::store::models::ControlTransaction; +/// Settings that override incremental windows; any of them rewrites history. +const CURSOR_SETTINGS: [&str; 6] = [ + "start_cursor", + "end_cursor", + "start_cursor_ts", + "end_cursor_ts", + "start_cursor_int", + "end_cursor_int", +]; + +/// Confirmations of destructive changes an ad-hoc command may set; a published job's authored settings keep job.run. +const ADHOC_DESTRUCTIVE_SETTINGS: [&str; 4] = [ + "allow_snapshot_full_refresh", + "allow_snapshot_schema_change", + "allow_table_type_downgrade", + "allow_retention_decrease", +]; + pub(crate) async fn authorize_execution( mut transaction: ControlTransaction, (context, credentials): (&AuditContext, Uuid), @@ -18,48 +38,8 @@ pub(crate) async fn authorize_execution( if request.kind == RunKind::Group { (transaction, executions) = frozen_group_children(transaction, request).await?; } - let mut scope = PermissionScope { - action, - location_id: Some(request.location_id), - target: Some(request.target.clone()), - nodes: executions - .iter() - .flat_map(|run| run.write_set.iter().cloned()) - .collect(), - }; - let (mut transaction, mut context) = authorize_person( - transaction, - (context, credentials), - &scope, - DefaultRole::None, - ) - .await?; - if action == Action::RunRerun && request.kind == RunKind::Adhoc { - scope.action = Action::RunAdhoc; - (transaction, context) = authorize_person( - transaction, - (&context, credentials), - &scope, - DefaultRole::None, - ) - .await?; - } - if matches!(action, Action::JobRun | Action::RunRerun) - && (request.window.is_some() - || executions.iter().any(|run| { - run.settings - .get("full_refresh") - .and_then(serde_json::Value::as_bool) - == Some(true) - || run.settings.contains_key("start_cursor") - || run.settings.contains_key("end_cursor") - || run.settings.contains_key("start_cursor_ts") - || run.settings.contains_key("end_cursor_ts") - || run.settings.contains_key("start_cursor_int") - || run.settings.contains_key("end_cursor_int") - })) - { - scope.action = Action::RunRewriteHistory; + let mut context = context.clone(); + for scope in execution_scopes(request, &executions, action) { (transaction, context) = authorize_person( transaction, (&context, credentials), @@ -70,3 +50,65 @@ pub(crate) async fn authorize_execution( } Ok((transaction, context)) } + +/// Every scope `action` on `request` needs, in check order; pure, so a dry run decides the same scopes without auditing. +pub(crate) fn execution_scopes( + request: &RunRequest, + executions: &[RunRequest], + action: Action, +) -> Vec { + let nodes: BTreeSet = if request.kind == RunKind::Adhoc { + request.selection.clone() + } else { + executions + .iter() + .flat_map(|run| run.write_set.iter().cloned()) + .collect() + }; + let launches = matches!(action, Action::JobRun | Action::RunRerun | Action::RunAdhoc); + let adhoc = request.kind == RunKind::Adhoc; + let mut actions: Vec = vec![action]; + if action == Action::RunRerun && adhoc { + actions.push(Action::RunAdhoc); + } + if launches && rewrites_history(request, executions) { + actions.push(Action::RunRewriteHistory); + } + if launches && adhoc && request.settings.contains_key("warehouse") { + actions.push(Action::RunWarehouse); + } + if launches && request.priority > 0 { + actions.push(Action::RunPrioritise); + } + actions + .into_iter() + .map(|action| PermissionScope { + action, + location_id: Some(request.location_id), + target: Some(request.target.clone()), + nodes: nodes.clone(), + }) + .collect() +} + +fn rewrites_history(request: &RunRequest, executions: &[RunRequest]) -> bool { + if request.window.is_some() { + return true; + } + for run in executions { + let mut names: Vec<&str> = vec!["full_refresh"]; + if run.kind == RunKind::Adhoc { + names.extend(ADHOC_DESTRUCTIVE_SETTINGS); + } + let enabled = names + .iter() + .any(|name| run.settings.get(*name).and_then(serde_json::Value::as_bool) == Some(true)); + let cursor = CURSOR_SETTINGS + .iter() + .any(|name| run.settings.contains_key(*name)); + if enabled || cursor { + return true; + } + } + false +} diff --git a/crates/sqlbuild-orchestration/src/runs/constants.rs b/crates/sqlbuild-orchestration/src/runs/constants.rs index 4d273c9a15..036eccdf10 100644 --- a/crates/sqlbuild-orchestration/src/runs/constants.rs +++ b/crates/sqlbuild-orchestration/src/runs/constants.rs @@ -14,3 +14,8 @@ pub(crate) const RECALL_INVOCATION_ID_BYTES: usize = 256; /// Manual requests derive their audit event ID from the run ID so retries find the original. pub(crate) const MANUAL_JOB_AUDIT_TAG: &[u8] = b"manual-job-request"; pub(crate) const RERUN_AUDIT_TAG: &[u8] = b"rerun-request"; +/// Verbs a worker runs today; other verbs parse, resolve and authorise but are refused at launch. +pub(crate) const LAUNCHABLE_VERBS: [&str; 1] = ["build"]; + +/// Ad-hoc requests derive their audit event ID from the run ID so retries find the original. +pub(crate) const ADHOC_AUDIT_TAG: &[u8] = b"adhoc-run-request"; diff --git a/crates/sqlbuild-orchestration/src/runs/main/check_adhoc_run.rs b/crates/sqlbuild-orchestration/src/runs/main/check_adhoc_run.rs new file mode 100644 index 0000000000..25ed102225 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/runs/main/check_adhoc_run.rs @@ -0,0 +1,64 @@ +//! A dry run resolves and decides a command exactly as a launch would, but queues and audits nothing. + +use uuid::Uuid; + +use crate::credentials::models::AuditContext; +use crate::runs::_helpers::requests::adhoc::{ + frozen_adhoc_request, may_view_target, permission_decisions, +}; +use crate::runs::constants::LAUNCHABLE_VERBS; +use crate::runs::models::{AdhocCheck, AdhocCheckRequest}; +use crate::runs::types::CommandPlanner; +use crate::store::errors::ControlError; +use crate::store::main::{begin::begin, commit::commit}; +use crate::store::models::{ControlStore, TransactionMode}; + +/// Without view on every current node the dry run reveals nothing, and it never records a denial. +pub async fn check_adhoc_run( + store: &ControlStore, + (context, principal_id): (&AuditContext, Uuid), + planner: &dyn CommandPlanner, + request: &AdhocCheckRequest, +) -> Result { + let transaction = begin(store, TransactionMode::Read).await?; + let (transaction, viewing) = may_view_target(transaction, principal_id, request).await?; + let (transaction, frozen) = if viewing { + frozen_adhoc_request(transaction, (context, planner), (request, Uuid::nil())).await? + } else { + ( + transaction, + Err("view on every node of this target is required".to_owned()), + ) + }; + let (verb, frozen) = match frozen { + Ok(frozen) => frozen, + Err(refusal) => { + commit(transaction).await?; + return Ok(AdhocCheck { + snapshot_id: None, + verb: None, + selection: Vec::new(), + write_set: Vec::new(), + settings: Default::default(), + permissions: Vec::new(), + error: Some(refusal), + launchable: false, + }); + } + }; + let (transaction, permissions) = + permission_decisions(transaction, principal_id, &frozen).await?; + commit(transaction).await?; + let error = (!LAUNCHABLE_VERBS.contains(&verb.as_str())) + .then(|| format!("workers do not run sqb {verb} yet; only build can launch")); + Ok(AdhocCheck { + snapshot_id: Some(frozen.snapshot_id), + launchable: error.is_none() && permissions.iter().all(|permission| permission.allowed), + verb: Some(verb), + selection: frozen.selection.into_iter().collect(), + write_set: frozen.write_set.into_iter().collect(), + settings: frozen.settings, + permissions, + error, + }) +} diff --git a/crates/sqlbuild-orchestration/src/runs/main/mod.rs b/crates/sqlbuild-orchestration/src/runs/main/mod.rs index ce37545bfb..a062e002e1 100644 --- a/crates/sqlbuild-orchestration/src/runs/main/mod.rs +++ b/crates/sqlbuild-orchestration/src/runs/main/mod.rs @@ -1,6 +1,6 @@ pub mod advance_groups; -pub mod cancel_person_run; pub mod change_run; +pub mod check_adhoc_run; pub mod claim_next_run; pub(crate) mod frozen_job_request; pub mod heartbeat_run; @@ -15,6 +15,6 @@ pub mod publish_run_output; pub mod publish_run_recall; pub(crate) mod queue_run; pub mod request_job_run; -pub mod request_person_job_run; +pub mod request_person_run; pub mod request_rerun; pub mod run_execution; diff --git a/crates/sqlbuild-orchestration/src/runs/main/request_person_job_run.rs b/crates/sqlbuild-orchestration/src/runs/main/request_person_job_run.rs deleted file mode 100644 index 60259dd4e5..0000000000 --- a/crates/sqlbuild-orchestration/src/runs/main/request_person_job_run.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Browser callers cannot take the trusted local-owner path or supply an execution write set. - -use uuid::Uuid; - -use crate::credentials::models::AuditContext; -use crate::runs::_helpers::requests::manual_job::request_job_run_in; -use crate::runs::models::JobRunRequest; -use crate::store::errors::ControlError; -use crate::store::main::write::write; -use crate::store::models::ControlStore; - -pub async fn request_person_job_run( - store: &ControlStore, - (context, credentials): (&AuditContext, Uuid), - request: &JobRunRequest, -) -> Result { - write(store, |transaction| { - request_job_run_in(transaction, (context, Some(credentials)), request) - }) - .await -} diff --git a/crates/sqlbuild-orchestration/src/runs/main/request_person_run.rs b/crates/sqlbuild-orchestration/src/runs/main/request_person_run.rs new file mode 100644 index 0000000000..c88868b7e0 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/runs/main/request_person_run.rs @@ -0,0 +1,37 @@ +//! Browser callers cannot take the trusted local-owner path or supply an execution write set. + +use uuid::Uuid; + +use crate::credentials::models::AuditContext; +use crate::runs::_helpers::requests::adhoc::request_adhoc_run_in; +use crate::runs::_helpers::requests::cancel::cancel_person_run; +use crate::runs::_helpers::requests::manual_job::request_job_run_in; +use crate::runs::models::PersonRunRequest; +use crate::store::errors::ControlError; +use crate::store::main::write::write; +use crate::store::models::ControlStore; + +/// A person queues a published job or an ad-hoc command, or cancels a run; each is authorised on the nodes it covers. +pub async fn request_person_run( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), + request: PersonRunRequest<'_>, +) -> Result { + match request { + PersonRunRequest::Cancel(request) => { + cancel_person_run(store, (context, credentials), request).await + } + PersonRunRequest::Job(request) => { + write(store, |transaction| async move { + request_job_run_in(transaction, (context, Some(credentials)), request).await + }) + .await + } + PersonRunRequest::Adhoc(planner, request) => { + write(store, |transaction| async move { + request_adhoc_run_in(transaction, (context, credentials), (planner, request)).await + }) + .await + } + } +} diff --git a/crates/sqlbuild-orchestration/src/runs/mod.rs b/crates/sqlbuild-orchestration/src/runs/mod.rs index 529a59766a..40b3232c61 100644 --- a/crates/sqlbuild-orchestration/src/runs/mod.rs +++ b/crates/sqlbuild-orchestration/src/runs/mod.rs @@ -2,6 +2,7 @@ pub(crate) mod _helpers; pub(crate) mod constants; pub mod main; pub mod models; +pub mod types; #[cfg(test)] #[path = "../../tests/runs/test_contracts.rs"] diff --git a/crates/sqlbuild-orchestration/src/runs/models.rs b/crates/sqlbuild-orchestration/src/runs/models.rs index 545660e12d..5394fd2776 100644 --- a/crates/sqlbuild-orchestration/src/runs/models.rs +++ b/crates/sqlbuild-orchestration/src/runs/models.rs @@ -6,6 +6,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::credentials::models::Action; +use crate::runs::types::CommandPlanner; + #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] #[serde(rename_all = "snake_case")] pub enum RunKind { @@ -213,6 +216,100 @@ pub struct RunNodeRecord { pub skipped_because_of: Option, } +/// Recall event metadata of a run. Payloads are left out: they can carry SQL, request settings and error text. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RunEventRecord { + pub storage_id: i64, + pub event_id: String, + pub invocation_id: String, + pub occurred_at: String, + pub event_type: String, + pub severity: Option, + pub producer: Option, + pub resource_id: Option, + pub resource_kind: Option, + pub resource_name: Option, + pub resource_attempt_id: Option, + pub statement_id: Option, + pub error_type: Option, + pub error_code: Option, + pub duration_ms: Option, + pub outcome: Option, + pub row_count: Option, + pub affected_rows: Option, +} + +/// Warehouse statement metadata of a run: kind, timing, counts and outcome, never the SQL text. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RunStatementRecord { + pub invocation_id: String, + pub statement_id: String, + pub resource_id: Option, + pub resource_name: Option, + pub resource_kind: Option, + pub intent: Option, + pub statement_kind: Option, + pub purpose: Option, + pub query_id: Option, + pub started_at: Option, + pub ended_at: Option, + pub outcome: Option, + pub error_type: Option, + pub error_code: Option, + pub duration_ms: Option, + pub row_count: Option, + pub affected_rows: Option, + pub sql_available: bool, + pub sql_unavailable_reason: Option, +} + +/// One statement's executed SQL; `sql` is null when capture was off or retention dropped it. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RunStatementSql { + pub statement_id: String, + pub sql: Option, +} + +/// Captured output and error text of a run's invocations; both can quote warehouse data. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RunOutput { + /// The newest captured lines per invocation, oldest first. + pub lines: Vec, + /// Invocation failures and error diagnostics, oldest first. + pub errors: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RunOutputLine { + pub invocation_id: String, + pub sequence: i64, + pub stream: String, + pub occurred_at: String, + pub text: String, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct RunErrorText { + pub invocation_id: String, + pub occurred_at: Option, + pub error_type: Option, + pub message: String, +} + +/// One build attempt of a node by a run in this target; the same metadata as the run's node list. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct NodeRunRecord { + pub run_id: Uuid, + pub job_key: Option, + pub queued_at: DateTime, + pub status: String, + pub started_at: Option>, + pub ended_at: Option>, + pub rows_affected: Option, + pub error_code: Option, + pub skipped_because_of: Option, +} + /// The database owns the claim before a launcher sees the pinned request. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] pub struct RunClaim { @@ -451,3 +548,72 @@ pub struct RunObservation { pub last_heartbeat_at: Option>, pub observed_at: DateTime, } + +/// A person's command as argument tokens after `sqb`, never shell text; selectors resolve on the server. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(deny_unknown_fields)] +pub struct AdhocRunRequest { + pub run_id: Uuid, + pub location_id: Uuid, + pub target: String, + pub command: Vec, + pub reason: String, + pub priority: i64, +} + +/// A dry run of a command; nothing is queued or audited. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +#[serde(deny_unknown_fields)] +pub struct AdhocCheckRequest { + pub location_id: Uuid, + pub target: String, + pub command: Vec, + pub priority: i64, +} + +/// One graph resource selectors resolve over: its `kind:name` key, upstream keys, tags and model folder. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PlannerResource { + pub key: String, + pub deps: Vec, + pub tags: Vec, + pub folder: Option, +} + +/// A parsed command: its verb, the exact node keys it selects and the flags it sets, by parser destination. +#[derive(Clone, Debug, PartialEq)] +pub struct CommandPlan { + pub verb: String, + pub selection: BTreeSet, + pub settings: BTreeMap, +} + +/// One permission the run needs and whether the person holds it on every resolved node. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct AdhocPermission { + pub action: Action, + pub allowed: bool, + /// Why it is refused, as the launch would report it. + pub reason: Option, +} + +/// What launching the command would run and whether this person may launch it. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, utoipa::ToSchema)] +pub struct AdhocCheck { + pub snapshot_id: Option, + pub verb: Option, + pub selection: Vec, + pub write_set: Vec, + pub settings: BTreeMap, + pub permissions: Vec, + /// Why the command cannot run regardless of permissions, such as a refused flag or an unknown selector. + pub error: Option, + pub launchable: bool, +} + +/// A person's change to the run queue: a published job, a command the planner resolves on the server, or a cancellation. +pub enum PersonRunRequest<'a> { + Job(&'a JobRunRequest), + Adhoc(&'a dyn CommandPlanner, &'a AdhocRunRequest), + Cancel(&'a CancelRunRequest), +} diff --git a/crates/sqlbuild-orchestration/src/runs/types.rs b/crates/sqlbuild-orchestration/src/runs/types.rs new file mode 100644 index 0000000000..fba6ad6c7f --- /dev/null +++ b/crates/sqlbuild-orchestration/src/runs/types.rs @@ -0,0 +1,19 @@ +//! The command planner the server is built with; the CLI owns the grammar and the selector resolver. + +use crate::runs::models::{CommandPlan, PlannerResource}; + +/// Parse `command` with the CLI's grammar and resolve its selectors over `resources`; pure and side-effect free. +pub trait CommandPlanner: Send + Sync { + /// The verb, exact node keys and validated settings, or why the command is refused. + fn plan( + &self, + command: &[String], + resources: &[PlannerResource], + ) -> Result; +} + +/// What the API hands runs to workers with: their Docker images and the command planner. +pub type RunHandoff = ( + Vec, + std::sync::Arc, +); diff --git a/crates/sqlbuild-orchestration/src/snapshots/main/publish_remote_snapshot.rs b/crates/sqlbuild-orchestration/src/snapshots/main/publish_remote_snapshot.rs index 681cc0f61e..f278709c98 100644 --- a/crates/sqlbuild-orchestration/src/snapshots/main/publish_remote_snapshot.rs +++ b/crates/sqlbuild-orchestration/src/snapshots/main/publish_remote_snapshot.rs @@ -1,10 +1,16 @@ //! A code location sends retained compiled data over HTTP and never opens the control store. +use reqwest::StatusCode; +use reqwest::header::{CONTENT_ENCODING, CONTENT_TYPE}; + +use crate::api::constants::{BODY_BYTES, GZIP_ENCODING, JSON_MEDIA_TYPE}; use crate::api::errors::ClientError; use crate::api::main::client_response::client_response; use crate::api::main::connect_client::connect_client; use crate::snapshots::models::{SnapshotInput, SnapshotRecord}; +use crate::store::main::compress::compress; +/// Sends the snapshot gzip-encoded, and once more as plain JSON when a server refuses the encoding and the plain body fits the wire bound. pub async fn publish_remote_snapshot( server_url: &str, bearer: &str, @@ -19,5 +25,25 @@ pub async fn publish_remote_snapshot( &input.target, "snapshots", ])?; - client_response(connection.client.post(url).json(input)).await + let body = serde_json::to_vec(input) + .map_err(|_| ClientError::Configuration("snapshot is not serializable as JSON"))?; + let gzipped = + compress(&body).map_err(|_| ClientError::Configuration("snapshot could not be gzipped"))?; + let request = |body: Vec| { + connection + .client + .post(url.clone()) + .header(CONTENT_TYPE, JSON_MEDIA_TYPE) + .body(body) + }; + match client_response(request(gzipped).header(CONTENT_ENCODING, GZIP_ENCODING)).await { + Err(ClientError::Rejected(status)) + if (status == StatusCode::BAD_REQUEST + || status == StatusCode::UNSUPPORTED_MEDIA_TYPE) + && body.len() <= BODY_BYTES => + { + client_response(request(body)).await + } + result => result, + } } diff --git a/crates/sqlbuild-orchestration/src/store/_helpers/mod.rs b/crates/sqlbuild-orchestration/src/store/_helpers/mod.rs index 507d3bc78e..420775f72e 100644 --- a/crates/sqlbuild-orchestration/src/store/_helpers/mod.rs +++ b/crates/sqlbuild-orchestration/src/store/_helpers/mod.rs @@ -5,3 +5,5 @@ pub(crate) mod open_sqlite; pub(crate) mod recall_rows; pub(crate) mod recall_schema; pub(crate) mod release_version; +#[cfg(test)] +pub(crate) mod statement_count; diff --git a/crates/sqlbuild-orchestration/src/store/_helpers/statement_count.rs b/crates/sqlbuild-orchestration/src/store/_helpers/statement_count.rs new file mode 100644 index 0000000000..4b4e2c2db0 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/store/_helpers/statement_count.rs @@ -0,0 +1,13 @@ +//! Test-only count of the store statements one task issues. + +use std::cell::Cell; + +tokio::task_local! { + /// The statements issued so far by a task that counts them. + pub(crate) static STATEMENTS: Cell; +} + +/// Count one statement when the current task counts them. +pub(crate) fn count_statement() { + let _ = STATEMENTS.try_with(|count| count.set(count.get() + 1)); +} diff --git a/crates/sqlbuild-orchestration/src/store/main/query.rs b/crates/sqlbuild-orchestration/src/store/main/query.rs index 9e79cdc7a1..f62a6d157c 100644 --- a/crates/sqlbuild-orchestration/src/store/main/query.rs +++ b/crates/sqlbuild-orchestration/src/store/main/query.rs @@ -10,6 +10,8 @@ pub(crate) async fn query( sql: &str, parameters: Vec, ) -> Result<(ControlTransaction, Vec), ControlError> { + #[cfg(test)] + crate::store::_helpers::statement_count::count_statement(); let connection = transaction .connection .take() diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/mod.rs b/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/mod.rs index 51c3c3a651..e3f0ef7cbf 100644 --- a/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/mod.rs +++ b/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/mod.rs @@ -1,3 +1,5 @@ pub(crate) mod checks; pub(crate) mod coverage; pub(crate) mod maintenance; +pub(crate) mod node_runs; +pub(crate) mod run_recall; diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/node_runs.rs b/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/node_runs.rs new file mode 100644 index 0000000000..633a2477fb --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/node_runs.rs @@ -0,0 +1,22 @@ +//! Bounded run metadata for one node; it never includes SQL, sampled rows or captured output. + +use uuid::Uuid; + +use crate::runs::models::NodeRunRecord; +use crate::store::errors::ControlError; +use crate::store::main::query_records::query_records; +use crate::store::models::{ControlTransaction, Parameter}; + +pub(crate) async fn node_runs_in( + transaction: ControlTransaction, + (location_id, target, node_key): (Uuid, &str, &str), + limit: u32, +) -> Result<(ControlTransaction, Vec), ControlError> { + query_records(transaction, + "SELECT r.run_id, r.job_key, r.queued_at, n.status, n.started_at, n.ended_at, n.rows_affected, n.error_code, \ + n.skipped_because_of FROM sqb_run_nodes n JOIN sqb_runs r ON r.run_id=n.run_id \ + WHERE r.location_id=$1 AND r.target=$2 AND n.node_key=$3 AND n.kind='node' \ + ORDER BY r.queued_at DESC, r.run_id DESC LIMIT $4", + vec![Parameter::Id(location_id), Parameter::Text(target.into()), Parameter::Text(node_key.into()), + Parameter::Integer(i64::from(limit.min(1000)))]).await +} diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/run_recall.rs b/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/run_recall.rs new file mode 100644 index 0000000000..eae24f60d4 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/_helpers/run_recall.rs @@ -0,0 +1,296 @@ +//! A run's Recall rows: the scheduler invocation and its claimed worker's invocations, with fixed column lists. + +use std::collections::BTreeSet; + +use serde_json::{Value, json}; +use uuid::Uuid; + +use crate::authorization::main::authorize_listing::authorize_listing; +use crate::authorization::main::authorize_person::authorize_person; +use crate::credentials::main::audit::append_audit::append_audit; +use crate::credentials::models::{Action, AuditContext, DefaultRole, PermissionScope}; +use crate::runs::models::{ + RunErrorText, RunEventRecord, RunOutput, RunOutputLine, RunStatementRecord, RunStatementSql, +}; +use crate::store::errors::ControlError; +use crate::store::main::decode_row::decode_row; +use crate::store::main::query::query; +use crate::store::main::query_records::query_records; +use crate::store::main::{begin::begin, commit::commit}; +use crate::store::models::{ControlStore, ControlTransaction, Parameter, TransactionMode}; + +/// The Recall invocations of one run and the nodes it selected. +pub(crate) struct RunRecallScope { + namespaces: Vec, + selection: BTreeSet, +} + +/// The run's transaction and scope, plus the deciding context and granted scope of a sensitive read. +pub(crate) type RunRecallRead = ( + ControlTransaction, + RunRecallScope, + Option<(AuditContext, PermissionScope)>, +); + +/// Listing view first so unknown runs look refused, then the run, then any sensitive action on its selection. +pub(crate) async fn begin_run_recall( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), + (location_id, target, run_id): (Uuid, &str, Uuid), + (default_role, sensitive): (DefaultRole, Option), +) -> Result, ControlError> { + let (transaction, context) = authorize_listing( + begin(store, TransactionMode::Write).await?, + (context, credentials), + (location_id, target), + default_role, + ) + .await?; + let (transaction, scope) = run_recall_scope(transaction, (location_id, target, run_id)).await?; + let Some(scope) = scope else { + commit(transaction).await?; + return Ok(None); + }; + let Some(action) = sensitive else { + return Ok(Some((transaction, scope, None))); + }; + let permission = PermissionScope { + action, + location_id: Some(location_id), + target: Some(target.into()), + nodes: scope.selection.clone(), + }; + let (transaction, context) = authorize_person( + transaction, + (&context, credentials), + &permission, + DefaultRole::None, + ) + .await?; + Ok(Some((transaction, scope, Some((context, permission))))) +} + +/// A sensitive run read, authorized for `action` and audited as `audit_action` in the same transaction. +pub(crate) async fn sensitive_run_read( + store: &ControlStore, + credentials: (&AuditContext, Uuid), + (run, default_role, action): ((Uuid, &str, Uuid), DefaultRole, Action), + (audit_action, read, details): ( + &str, + impl AsyncFnOnce( + ControlTransaction, + &RunRecallScope, + ) -> Result<(ControlTransaction, T), ControlError>, + impl FnOnce(&T) -> Value, + ), +) -> Result, ControlError> { + let Some((transaction, scope, Some((context, permission)))) = + begin_run_recall(store, credentials, run, (default_role, Some(action))).await? + else { + return Ok(None); + }; + let (transaction, result) = read(transaction, &scope).await?; + let transaction = append_audit( + transaction, + &context, + (Uuid::now_v7(), audit_action), + ( + json!(permission), + json!(null), + json!({"run_id": run.2, "read": details(&result)}), + ), + ) + .await?; + commit(transaction).await?; + Ok(Some(result)) +} + +/// `None` when the run is not in this location and target. +async fn run_recall_scope( + transaction: ControlTransaction, + (location_id, target, run_id): (Uuid, &str, Uuid), +) -> Result<(ControlTransaction, Option), ControlError> { + let (transaction, runs) = query( + transaction, + "SELECT selection_json FROM sqb_runs WHERE run_id=$1 AND location_id=$2 AND target=$3", + vec![ + Parameter::Id(run_id), + Parameter::Id(location_id), + Parameter::Text(target.into()), + ], + ) + .await?; + let Some(run) = runs.first() else { + return Ok((transaction, None)); + }; + let selection = match &run["selection_json"] { + Value::String(text) => serde_json::from_str(text), + other => serde_json::from_value(other.clone()), + } + .map_err(|error| ControlError::Invalid(error.to_string()))?; + let scheduler = format!("scheduler/{run_id}"); + let (transaction, claims) = query( + transaction, + "SELECT payload FROM recall_events WHERE invocation_id=$1 AND event_type='run_claimed' ORDER BY invocation_sequence DESC LIMIT 1", + vec![Parameter::Text(scheduler.clone())], + ) + .await?; + let mut namespaces = vec![scheduler]; + if let Some(claim_id) = claims + .first() + .and_then(|row| row["payload"]["claim"]["claim_id"].as_str()) + { + namespaces.push(format!("worker/{claim_id}")); + } + Ok(( + transaction, + Some(RunRecallScope { + namespaces, + selection, + }), + )) +} + +pub(crate) async fn run_events_in( + transaction: ControlTransaction, + scope: &RunRecallScope, + (after, limit): (i64, u32), +) -> Result<(ControlTransaction, Vec), ControlError> { + let (filter, mut parameters) = invocation_filter(scope, 1); + let next = parameters.len() + 1; + parameters.extend([ + Parameter::Integer(after), + Parameter::Integer(i64::from(limit.min(5000))), + ]); + query_records(transaction, + &format!("SELECT storage_id, event_id, invocation_id, occurred_at, event_type, severity, producer, resource_id, \ + resource_kind, resource_name, resource_attempt_id, statement_id, error_type, error_code, duration_ms, outcome, \ + row_count, affected_rows FROM events WHERE {filter} AND storage_id>${next} ORDER BY storage_id LIMIT ${}", next + 1), + parameters).await +} + +pub(crate) async fn run_statements_in( + transaction: ControlTransaction, + scope: &RunRecallScope, + limit: u32, +) -> Result<(ControlTransaction, Vec), ControlError> { + let (filter, mut parameters) = invocation_filter(scope, 1); + let next = parameters.len() + 1; + parameters.push(Parameter::Integer(i64::from(limit.min(5000)))); + let (transaction, rows) = query(transaction, + &format!("SELECT invocation_id, statement_id, resource_id, resource_name, resource_kind, intent, statement_kind, \ + purpose, query_id, started_at, ended_at, outcome, error_type, error_code, duration_ms, row_count, affected_rows, \ + sql_available, sql_unavailable_reason FROM statements WHERE {filter} ORDER BY started_at, statement_id LIMIT ${next}"), + parameters).await?; + let records = rows + .into_iter() + .map(|mut row| { + let available = row + .get("sql_available") + .is_some_and(|value| value.as_bool().unwrap_or_else(|| value.as_i64() == Some(1))); + row.insert("sql_available".into(), Value::Bool(available)); + decode_row(row) + }) + .collect::>()?; + Ok((transaction, records)) +} + +pub(crate) async fn statement_sql_in( + transaction: ControlTransaction, + scope: &RunRecallScope, + statement_id: &str, +) -> Result<(ControlTransaction, Option), ControlError> { + let (filter, mut parameters) = invocation_filter(scope, 1); + let next = parameters.len() + 1; + parameters.push(Parameter::Text(statement_id.into())); + let (transaction, statements) = query( + transaction, + &format!("SELECT statement_id FROM statements WHERE {filter} AND statement_id=${next}"), + parameters.clone(), + ) + .await?; + if statements.is_empty() { + return Ok((transaction, None)); + } + let (transaction, rows) = query(transaction, + &format!("SELECT text FROM artifacts WHERE {filter} AND statement_id=${next} AND kind='sql' LIMIT 1"), + parameters).await?; + let sql = rows + .first() + .map(|row| display_text(&row["text"])) + .transpose()?; + Ok(( + transaction, + Some(RunStatementSql { + statement_id: statement_id.into(), + sql: sql.flatten(), + }), + )) +} + +pub(crate) async fn run_output_in( + transaction: ControlTransaction, + scope: &RunRecallScope, +) -> Result<(ControlTransaction, RunOutput), ControlError> { + let (filter, parameters) = invocation_filter(scope, 1); + let (transaction, rows) = query(transaction, + &format!("SELECT invocation_id, sequence, stream, occurred_at, text FROM output_tail WHERE {filter} \ + ORDER BY occurred_at, invocation_id, sequence"), + parameters.clone()).await?; + let lines = rows + .into_iter() + .map(|mut row| { + let text = display_text(&row["text"])?.unwrap_or_default(); + row.insert("text".into(), Value::String(text)); + decode_row::(row) + }) + .collect::>()?; + let (transaction, failures) = query(transaction, + &format!("SELECT invocation_id, ended_at AS occurred_at, error_type, error_message AS message FROM invocations \ + WHERE {filter} AND error_message IS NOT NULL"), + parameters.clone()).await?; + let (transaction, diagnostics) = query( + transaction, + &format!( + "SELECT invocation_id, occurred_at, logger AS error_type, message FROM diagnostics \ + WHERE {filter} AND severity IN ('error','critical') AND message IS NOT NULL" + ), + parameters, + ) + .await?; + let mut errors: Vec = failures + .into_iter() + .chain(diagnostics) + .map(decode_row) + .collect::>()?; + errors.sort_by(|left, right| left.occurred_at.cmp(&right.occurred_at)); + Ok((transaction, RunOutput { lines, errors })) +} + +/// Exact scheduler and worker invocations, plus the worker's nested invocations. +fn invocation_filter(scope: &RunRecallScope, first: usize) -> (String, Vec) { + let mut clauses: Vec = Vec::new(); + let mut parameters: Vec = Vec::new(); + for namespace in &scope.namespaces { + clauses.push(format!("invocation_id=${}", first + parameters.len())); + parameters.push(Parameter::Text(namespace.clone())); + if namespace.starts_with("worker/") { + clauses.push(format!("invocation_id LIKE ${}", first + parameters.len())); + parameters.push(Parameter::Text(format!("{namespace}/%"))); + } + } + (format!("({})", clauses.join(" OR ")), parameters) +} + +/// Captured bytes decode lossily for display; text columns pass through. +fn display_text(value: &Value) -> Result, ControlError> { + match value { + Value::Null => Ok(None), + Value::String(text) => Ok(Some(text.clone())), + other => { + let bytes: Vec = serde_json::from_value(other.clone()) + .map_err(|error| ControlError::Invalid(error.to_string()))?; + Ok(Some(String::from_utf8_lossy(&bytes).into_owned())) + } + } +} diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/main/mod.rs b/crates/sqlbuild-orchestration/src/warehouse_history/main/mod.rs index 55736dc22c..b87929e1b4 100644 --- a/crates/sqlbuild-orchestration/src/warehouse_history/main/mod.rs +++ b/crates/sqlbuild-orchestration/src/warehouse_history/main/mod.rs @@ -4,3 +4,8 @@ pub mod read_person_check_sql; pub mod read_person_checks; pub mod read_person_maintenance; pub mod read_person_microbatch_coverage; +pub mod read_person_node_runs; +pub mod read_person_run_events; +pub mod read_person_run_output; +pub mod read_person_run_statement_sql; +pub mod read_person_run_statements; diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_node_runs.rs b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_node_runs.rs new file mode 100644 index 0000000000..d2481e9ad5 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_node_runs.rs @@ -0,0 +1,31 @@ +//! A node's run history needs view on every current node, as each run's node list does. + +use uuid::Uuid; + +use crate::authorization::main::authorize_listing::authorize_listing; +use crate::credentials::models::{AuditContext, DefaultRole}; +use crate::runs::models::NodeRunRecord; +use crate::store::errors::ControlError; +use crate::store::main::{begin::begin, commit::commit}; +use crate::store::models::{ControlStore, TransactionMode}; +use crate::warehouse_history::_helpers::node_runs::node_runs_in; + +/// The runs that built one node in this target, newest first; a key no run built returns an empty list. +pub async fn read_person_node_runs( + store: &ControlStore, + (context, credentials): (&AuditContext, Uuid), + (location_id, target, node_key): (Uuid, &str, &str), + (limit, default_role): (u32, DefaultRole), +) -> Result, ControlError> { + let (transaction, _) = authorize_listing( + begin(store, TransactionMode::Write).await?, + (context, credentials), + (location_id, target), + default_role, + ) + .await?; + let (transaction, runs) = + node_runs_in(transaction, (location_id, target, node_key), limit).await?; + commit(transaction).await?; + Ok(runs) +} diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_events.rs b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_events.rs new file mode 100644 index 0000000000..bc759e1e14 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_events.rs @@ -0,0 +1,27 @@ +//! A run's event metadata needs view on every current node, as its node list does; payloads are never read. + +use uuid::Uuid; + +use crate::credentials::models::{AuditContext, DefaultRole}; +use crate::runs::models::RunEventRecord; +use crate::store::errors::ControlError; +use crate::store::main::commit::commit; +use crate::store::models::ControlStore; +use crate::warehouse_history::_helpers::run_recall::{begin_run_recall, run_events_in}; + +/// Events after the storage cursor, oldest first; `None` when the run is not in this location and target. +pub async fn read_person_run_events( + store: &ControlStore, + credentials: (&AuditContext, Uuid), + (run, page): ((Uuid, &str, Uuid), (i64, u32)), + default_role: DefaultRole, +) -> Result>, ControlError> { + let Some((transaction, scope, _)) = + begin_run_recall(store, credentials, run, (default_role, None)).await? + else { + return Ok(None); + }; + let (transaction, events) = run_events_in(transaction, &scope, page).await?; + commit(transaction).await?; + Ok(Some(events)) +} diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_output.rs b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_output.rs new file mode 100644 index 0000000000..ec531d7939 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_output.rs @@ -0,0 +1,30 @@ +//! Run output and error text can quote warehouse data, so they need view.evidence on every node the run selected. + +use serde_json::json; +use uuid::Uuid; + +use crate::credentials::models::{Action, AuditContext, DefaultRole}; +use crate::runs::models::RunOutput; +use crate::store::errors::ControlError; +use crate::store::models::ControlStore; +use crate::warehouse_history::_helpers::run_recall::{run_output_in, sensitive_run_read}; + +/// `None` when the run is not in this location and target; each read is audited like check evidence. +pub async fn read_person_run_output( + store: &ControlStore, + credentials: (&AuditContext, Uuid), + run: (Uuid, &str, Uuid), + default_role: DefaultRole, +) -> Result, ControlError> { + sensitive_run_read( + store, + credentials, + (run, default_role, Action::ViewEvidence), + ( + "output.read", + run_output_in, + |output: &RunOutput| json!({"lines": output.lines.len(), "errors": output.errors.len()}), + ), + ) + .await +} diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statement_sql.rs b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statement_sql.rs new file mode 100644 index 0000000000..16d35ffee3 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statement_sql.rs @@ -0,0 +1,31 @@ +//! Statement SQL needs view.sql on every node the run selected, and each read is audited like node SQL. + +use serde_json::json; +use uuid::Uuid; + +use crate::credentials::models::{Action, AuditContext, DefaultRole}; +use crate::runs::models::RunStatementSql; +use crate::store::errors::ControlError; +use crate::store::models::ControlStore; +use crate::warehouse_history::_helpers::run_recall::{sensitive_run_read, statement_sql_in}; + +/// `None` when the run is not here or the statement is not one of its own. +pub async fn read_person_run_statement_sql( + store: &ControlStore, + credentials: (&AuditContext, Uuid), + (run, statement_id): ((Uuid, &str, Uuid), &str), + default_role: DefaultRole, +) -> Result, ControlError> { + Ok(sensitive_run_read( + store, + credentials, + (run, default_role, Action::ViewSql), + ( + "sql.read", + async |transaction, scope| statement_sql_in(transaction, scope, statement_id).await, + |sql: &Option| json!({"statement_id": statement_id, "found": sql.is_some()}), + ), + ) + .await? + .flatten()) +} diff --git a/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statements.rs b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statements.rs new file mode 100644 index 0000000000..4c6b5b6179 --- /dev/null +++ b/crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statements.rs @@ -0,0 +1,27 @@ +//! A run's statement metadata needs view on every current node; the SQL text has its own read. + +use uuid::Uuid; + +use crate::credentials::models::{AuditContext, DefaultRole}; +use crate::runs::models::RunStatementRecord; +use crate::store::errors::ControlError; +use crate::store::main::commit::commit; +use crate::store::models::ControlStore; +use crate::warehouse_history::_helpers::run_recall::{begin_run_recall, run_statements_in}; + +/// `None` when the run is not in this location and target. +pub async fn read_person_run_statements( + store: &ControlStore, + credentials: (&AuditContext, Uuid), + (run, limit): ((Uuid, &str, Uuid), u32), + default_role: DefaultRole, +) -> Result>, ControlError> { + let Some((transaction, scope, _)) = + begin_run_recall(store, credentials, run, (default_role, None)).await? + else { + return Ok(None); + }; + let (transaction, statements) = run_statements_in(transaction, &scope, limit).await?; + commit(transaction).await?; + Ok(Some(statements)) +} diff --git a/crates/sqlbuild-orchestration/src/webhooks/_helpers/events.rs b/crates/sqlbuild-orchestration/src/webhooks/_helpers/events.rs index 50c830a7a8..2893510c0c 100644 --- a/crates/sqlbuild-orchestration/src/webhooks/_helpers/events.rs +++ b/crates/sqlbuild-orchestration/src/webhooks/_helpers/events.rs @@ -1,6 +1,6 @@ //! Source-specific readers retain authority; delivery owns only acknowledgements. -use crate::authorization::main::read_audit_events::read_audit_events; +use crate::credentials::main::audit::read_audit_events::read_audit_events; use crate::platform::main::read_platform_events::read_platform_events; use crate::store::errors::ControlError; use crate::store::models::ControlTransaction; @@ -20,7 +20,8 @@ pub(crate) async fn read_events( )) } WebhookSource::SecurityAudit => { - let (transaction, events) = read_audit_events(transaction, (after, 100)).await?; + let (transaction, events) = + read_audit_events(transaction, ((after, None), 100)).await?; Ok(( transaction, events diff --git a/crates/sqlbuild-orchestration/tests/api/helpers.rs b/crates/sqlbuild-orchestration/tests/api/helpers.rs index daa60882a2..f63874ad36 100644 --- a/crates/sqlbuild-orchestration/tests/api/helpers.rs +++ b/crates/sqlbuild-orchestration/tests/api/helpers.rs @@ -3,6 +3,7 @@ use std::time::Duration; use crate::api::main::serve_api::serve_api; use crate::api::models::ApiAuthentication; use crate::api::tests::test_types::TestApi; +use crate::runs::tests::test_types::ExactKeyPlanner; use crate::store::models::ControlStore; pub(crate) async fn start_api(store: &ControlStore) -> TestApi { @@ -22,7 +23,11 @@ pub(crate) async fn start_api_with_authentication( let task = tokio::spawn(serve_api( store, listener, - (authentication, Vec::new(), Default::default()), + ( + authentication, + (Vec::new(), std::sync::Arc::new(ExactKeyPlanner)), + Default::default(), + ), (|| {}, async { let _ = signal.await; }), @@ -101,3 +106,53 @@ pub(crate) fn raw_get(address: &str, path: &str) -> (u16, String) { .expect("numeric status"); (status, body.to_owned()) } + +/// Gzip members that decompress to a JSON string field longer than the decompressed bound. +pub(crate) fn gzip_bomb() -> Vec { + let filler = crate::store::main::compress::compress(&vec![b'a'; 1024 * 1024]).expect("gzip"); + let mut body = crate::store::main::compress::compress(br#"{"padding":""#).expect("gzip"); + for _ in 0..=crate::api::constants::DECOMPRESSED_BODY_BYTES / (1024 * 1024) { + body.extend_from_slice(&filler); + } + body +} + +/// Plain JSON one byte longer than the wire bound. +pub(crate) fn oversized_json() -> Vec { + let mut body = br#"{"padding":""#.to_vec(); + body.resize(crate::api::constants::BODY_BYTES - 1, b'a'); + body.extend_from_slice(br#""}"#); + body +} + +pub(crate) fn gzipped_empty_object() -> Vec { + crate::store::main::compress::compress(b"{}").expect("gzip") +} + +pub(crate) fn not_gzip() -> Vec { + b"{}".to_vec() +} + +/// A DuckShop-10k-sized snapshot whose plain JSON is larger than the wire bound. +pub(crate) fn large_snapshot(location_id: uuid::Uuid) -> crate::snapshots::models::SnapshotInput { + let mut input = crate::runs::tests::helpers::snapshot(location_id); + input.nodes = (0..10_282) + .map(|index| { + let sql = format!( + "select {index} as order_id, '{}' as note from orders_{index}", + "x".repeat(3_400) + ); + ( + format!("model:orders_{index}"), + crate::snapshots::models::SnapshotNode { + kind: "model".into(), + definition: serde_json::json!({"materialization": "table"}), + raw_sql: Some(sql.clone()), + compiled_sql: Some(sql), + python_source: None, + }, + ) + }) + .collect(); + input +} diff --git a/crates/sqlbuild-orchestration/tests/api/node_status/helpers.rs b/crates/sqlbuild-orchestration/tests/api/node_status/helpers.rs index a07b031bbd..00d6459944 100644 --- a/crates/sqlbuild-orchestration/tests/api/node_status/helpers.rs +++ b/crates/sqlbuild-orchestration/tests/api/node_status/helpers.rs @@ -1,6 +1,7 @@ //! Real stored evidence never substitutes observation clocks for consumed inputs. use serde_json::{Value, json}; +use std::cell::Cell; use uuid::Uuid; use crate::credentials::models::{AuditContext, DefaultRole}; @@ -8,6 +9,7 @@ use crate::runs::tests::helpers::snapshot; use crate::snapshots::main::publish_snapshot::publish_snapshot; use crate::snapshots::models::SnapshotInput; use crate::snapshots::models::SnapshotNode; +use crate::store::_helpers::statement_count::STATEMENTS; use crate::store::errors::ControlError; use crate::store::main::{begin::begin, commit::commit, query::query}; use crate::store::models::{ControlStore, Parameter, TransactionMode}; @@ -19,6 +21,9 @@ use crate::warehouse_metadata::main::{ }; use crate::warehouse_metadata::models::{MetadataBatch, NodeStatusQuery}; +/// Statements a catalog listing issues besides one build read per 100 nodes: 7 fixed, 1 per extra namespace shape. +const WIDE_LISTING_FIXED_STATEMENTS: usize = 8; + pub(crate) async fn node_status_reads( store: &ControlStore, credentials: (&AuditContext, Uuid), @@ -243,7 +248,12 @@ pub(crate) async fn node_status_reads( ); assert!(!json!(listed).to_string().contains("metadata_json")); listing_ignores_build_history(store, credentials, (location, &listed)).await; - listing_reads_wide_targets_in_batches(store, credentials, (location, input.clone())).await; + listing_wide_targets_is_batched_without_super_linear_growth( + store, + credentials, + (location, input.clone()), + ) + .await; assert!(matches!( read_person_node_status( store, @@ -358,8 +368,8 @@ async fn listing_ignores_build_history( ); } -/// Three thousand built nodes, half in a named database, keep their own builds, and listing them grows linearly. -async fn listing_reads_wide_targets_in_batches( +/// Three thousand nodes, half in a named database, keep their own builds, read 100 per statement without super-linear growth. +async fn listing_wide_targets_is_batched_without_super_linear_growth( store: &ControlStore, credentials: (&AuditContext, Uuid), (location, mut input): (Uuid, SnapshotInput), @@ -367,33 +377,36 @@ async fn listing_reads_wide_targets_in_batches( let names: Vec = (0..3_000).map(|index| format!("wide_{index:04}")).collect(); let mut medians: Vec = Vec::new(); for (first, size) in [(0, 1_000), (1_000, names.len())] { - medians.push( - list_wide_target( - store, - credentials, - (location, &mut input), - (&names, first, size), - ) - .await, + let (median, statements) = list_wide_target( + store, + credentials, + (location, &mut input), + (&names, first, size), + ) + .await; + assert!( + statements <= size.div_ceil(100) + WIDE_LISTING_FIXED_STATEMENTS, + "listing {size} nodes issued {statements} store statements; builds must be read 100 or more nodes per statement" ); + medians.push(median); } let ratio: f64 = medians[1].as_secs_f64() / medians[0].as_secs_f64(); assert!( ratio < 6.0, - "listing {} nodes took {:?}, {ratio:.1} times the {:?} for 1,000; listing must stay linear", + "listing {} nodes took {:?}, {ratio:.1} times the {:?} for 1,000; listing must not grow super-linearly", names.len(), medians[1], medians[0] ); } -/// Add `names[first..size]` with one build each and return the median time to list the first `size`. +/// Add `names[first..size]` with one build each; return the median listing time and one listing's statement count. async fn list_wide_target( store: &ControlStore, credentials: (&AuditContext, Uuid), (location, input): (Uuid, &mut SnapshotInput), (names, first, size): (&[String], usize, usize), -) -> std::time::Duration { +) -> (std::time::Duration, usize) { let database = |index: usize| (index % 2 == 1).then(|| "analytics_db".to_owned()); let built_at = |index: usize| { chrono::DateTime::parse_from_rfc3339("2026-02-01T00:00:00Z") @@ -492,5 +505,18 @@ async fn list_wide_target( assert_eq!(built, size, "every wide node keeps its own build"); } timings.sort(); - timings[1] + let statements = STATEMENTS + .scope(Cell::new(0), async { + list_person_catalog_nodes( + store, + credentials, + (location, "development"), + DefaultRole::None, + ) + .await + .expect("counted wide catalog"); + STATEMENTS.with(Cell::get) + }) + .await; + (timings[1], statements) } diff --git a/crates/sqlbuild-orchestration/tests/api/person_sessions/test_types.rs b/crates/sqlbuild-orchestration/tests/api/person_sessions/test_types.rs index 552889f4b8..a1a50591dd 100644 --- a/crates/sqlbuild-orchestration/tests/api/person_sessions/test_types.rs +++ b/crates/sqlbuild-orchestration/tests/api/person_sessions/test_types.rs @@ -16,5 +16,8 @@ pub(crate) struct SessionRoleCase { pub(crate) expected_metadata: u16, pub(crate) expected_connection: u16, pub(crate) expected_job: u16, + /// A job run above priority 0 needs `run.prioritise`, which only Admin grants. + pub(crate) expected_prioritised: u16, pub(crate) expected_sql: u16, + pub(crate) expected_admin: u16, } diff --git a/crates/sqlbuild-orchestration/tests/api/test_contracts.rs b/crates/sqlbuild-orchestration/tests/api/test_contracts.rs index b59038211b..9265f0c7f9 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_contracts.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_contracts.rs @@ -70,6 +70,8 @@ mod test_person_location_work; mod test_person_sessions; #[path = "test_proxy.rs"] mod test_proxy; +#[path = "test_publish_encoding.rs"] +mod test_publish_encoding; #[path = "test_reads.rs"] mod test_reads; #[path = "test_statistics_recovery.rs"] diff --git a/crates/sqlbuild-orchestration/tests/api/test_denials.rs b/crates/sqlbuild-orchestration/tests/api/test_denials.rs index d2cb06ea01..c3fe067e9e 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_denials.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_denials.rs @@ -20,9 +20,10 @@ use crate::store::tests::store_helpers::{close_test_store, open_test_store, run} use crate::store::tests::store_test_types::Backend; /// Location-scoped reads and the action each needs. -const READS: [(&str, &str); 12] = [ +const READS: [(&str, &str); 13] = [ ("runs", "view"), ("nodes", "view"), + ("nodes/orders/runs", "view"), ("jobs", "view"), ("triggers", "view"), ("changes", "view"), @@ -132,6 +133,28 @@ fn given_refused_requests_when_people_send_them_then_no_node_is_named_and_unknow ) }) .collect(); + for part in [ + "events", + "statements", + "statements/sql?statement_id=s", + "output", + ] { + cases.push(( + format!("run {part}"), + "view", + vec![ + ( + read(inventory, &format!("runs/{}/{part}", queued.run_id)), + None, + ), + (read(inventory, &format!("runs/{unknown_run}/{part}")), None), + ( + read(roles.location_id, &format!("runs/{unknown_run}/{part}")), + None, + ), + ], + )); + } cases.extend([ ( "run nodes".to_owned(), diff --git a/crates/sqlbuild-orchestration/tests/api/test_location_health.rs b/crates/sqlbuild-orchestration/tests/api/test_location_health.rs index db5ebfe4fa..928b2759ca 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_location_health.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_location_health.rs @@ -1,5 +1,7 @@ +use crate::authorization::main::bootstrap_user::bootstrap_user; use crate::credentials::main::tokens::issue_token::issue_token; -use crate::credentials::models::{AuditContext, CredentialScope}; +use crate::credentials::models::{AuditContext, CredentialScope, DefaultRole}; +use crate::platform::main::read_person_location_health::read_person_location_health; use crate::platform::main::report_remote_location_health::report_remote_location_health; use crate::platform::models::{LocationHealth, LocationHealthObservation, LocationHealthReport}; use crate::snapshots::main::publish_snapshot::publish_snapshot; @@ -171,6 +173,35 @@ fn given_scoped_location_observations_when_retried_or_read_then_history_and_auth ); assert_eq!(observations[0].report, connection); assert_eq!(observations[1].report, heartbeat); + let audit = AuditContext { + actor: "test-admin".into(), + granting_role: "admin".into(), + request_id: Uuid::now_v7(), + ip_address: None, + }; + let administrator = bootstrap_user(store, &audit, "orders-admin") + .await + .expect("administrator"); + let person_read = |target: &'static str| { + read_person_location_health( + store, + (&audit, administrator), + (location_id, target), + DefaultRole::None, + ) + }; + assert_eq!( + person_read("preview #1").await.expect("person health"), + observations, + "a person sees the same observations for the reporting target" + ); + assert!( + person_read("another target") + .await + .expect("other target health") + .is_empty(), + "another target's read leaves this target's observations out" + ); let events = read_recall_events(store, 0, 100) .await .expect("read durable events"); @@ -191,6 +222,44 @@ fn given_scoped_location_observations_when_retried_or_read_then_history_and_auth .all(|observation| observation.received_at >= before && observation.received_at <= Utc::now()) ); + let mut busy = snapshot(location_id); + busy.target = "another target".into(); + publish_snapshot(store, &busy) + .await + .expect("busy target generation"); + let busy_heartbeat = LocationHealthReport { + event_id: Uuid::now_v7(), + session_id: Uuid::now_v7(), + target: busy.target.clone(), + health: LocationHealth::Heartbeat {}, + }; + report_remote_location_health((&api.url, &token.bearer), location_id, &busy_heartbeat) + .await + .expect("busy target instance"); + for _ in 0..101 { + let busy_connection = LocationHealthReport { + event_id: Uuid::now_v7(), + health: LocationHealth::Connection { + generation: 1, + healthy: true, + }, + ..busy_heartbeat.clone() + }; + report_remote_location_health( + (&api.url, &token.bearer), + location_id, + &busy_connection, + ) + .await + .expect("busy target observation"); + } + assert_eq!( + person_read("preview #1") + .await + .expect("quiet target health"), + observations, + "a busier target's newer observations do not push out this target's" + ); stop_api(api).await; close_test_store(fixture).await; }); diff --git a/crates/sqlbuild-orchestration/tests/api/test_person_live.rs b/crates/sqlbuild-orchestration/tests/api/test_person_live.rs index b8abf89554..600de855ad 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_person_live.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_person_live.rs @@ -216,6 +216,33 @@ fn given_people_with_different_roles_when_they_bootstrap_and_subscribe_then_only .await .expect("disabled bootstrap") .status(); + let people: Value = client + .get(format!("{}/api/admin/people", api.url)) + .header(header::HOST, "control.example.com") + .header( + header::COOKIE, + format!("__Host-sqb-session={}", roles.administrator.bearer), + ) + .send() + .await + .expect("people") + .json() + .await + .expect("people body"); + let member = people + .as_array() + .into_iter() + .flatten() + .find(|person| person["principal_id"] == json!(roles.member_id)) + .expect("disabled member is listed"); + assert_eq!( + ( + member["disabled_at"].is_string(), + &member["active_sessions"] + ), + (true, &json!(0)), + "a disabled person's sessions no longer count as active" + ); let logout = client .post(format!("{}/api/auth/logout", api.url)) .header(header::HOST, "control.example.com") diff --git a/crates/sqlbuild-orchestration/tests/api/test_person_sessions.rs b/crates/sqlbuild-orchestration/tests/api/test_person_sessions.rs index cc253368c7..d94cd61b45 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_person_sessions.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_person_sessions.rs @@ -186,6 +186,7 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ let mut last_request = Value::Null; let mut listed_person_runs = 0; let mut launched_person_runs = 0; + let mut prioritised_runs: Vec = Vec::new(); let mut listed_seed_runs = 0; for role_case in roles { for role in role_case.role.iter().copied() { @@ -260,7 +261,10 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ ("parent_run_id", Value::Null), ("trigger_name", Value::Null), ("rerun_of_run_id", Value::Null), - ("priority", json!(0)), + ( + "priority", + json!(i64::from(prioritised_runs.contains(&launched["run_id"]))), + ), ("rewrite_history", json!(false)), ("cancel_requested_at", Value::Null), ] { @@ -286,9 +290,13 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ let known_reads = [ format!("{metadata}/{}/nodes", seed_run.run_id), format!("/api/control/locations/{location}/targets/development/nodes"), + format!( + "/api/control/locations/{location}/targets/development/nodes/orders/runs" + ), format!("/api/control/locations/{location}/targets/development/jobs"), format!("/api/control/locations/{location}/targets/development/triggers"), format!("/api/control/locations/{location}/targets/development/snapshots"), + format!("/api/control/locations/{location}/targets/development/health"), format!( "/api/control/locations/{location}/targets/development/triggers/{}/ticks", trigger.trigger_id @@ -321,6 +329,69 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ "{read_path}" ); } + let granted = usize::from(role_case.expected_admin == StatusCode::OK.as_u16()); + let mut admin_reads = Vec::new(); + for admin_read in [ + "/api/admin/people".to_owned(), + "/api/admin/roles".to_owned(), + format!("/api/admin/locations/{location}/targets/development/roles"), + ] { + let listed = request( + &provider.client, + session, + (Method::GET, &admin_read), + (&Value::Null, &session.csrf), + ) + .await; + assert_eq!( + listed.status().as_u16(), + role_case.expected_admin, + "{admin_read}" + ); + admin_reads.push(listed.json::().await.expect("admin read")); + } + let own: Vec<&Value> = admin_reads[0] + .as_array() + .into_iter() + .flatten() + .filter(|entry| entry["principal_id"] == json!(person)) + .collect(); + assert_eq!(own.len(), granted, "an administrator lists the person"); + for entry in own { + let mut keys: Vec<&str> = entry + .as_object() + .expect("person") + .keys() + .map(String::as_str) + .collect(); + keys.sort_unstable(); + assert_eq!( + keys, + [ + "active_sessions", + "created_at", + "disabled_at", + "groups", + "has_password", + "last_signed_in_at", + "name", + "principal_id" + ], + "sign-in facts only, never secrets" + ); + assert!(entry["active_sessions"].as_i64() >= Some(1)); + } + assert_eq!( + admin_reads[1]["assignments"] + .as_array() + .into_iter() + .flatten() + .filter(|entry| entry["principal_id"] == json!(person) + && entry["role_key"] == "admin") + .count(), + granted, + "an administrator sees the person's admin assignment" + ); let jobs: Value = request( &provider.client, session, @@ -347,6 +418,49 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ expected_jobs[..expected_jobs.len() * usize::from(jobs.is_array())], "jobs list declared metadata and never settings" ); + let node_runs: Value = request( + &provider.client, + session, + ( + Method::GET, + &format!("/api/control/locations/{location}/targets/development/nodes/orders/runs"), + ), + (&Value::Null, &session.csrf), + ) + .await + .json() + .await + .expect("node runs response"); + let seeded_node_runs: Vec<&Value> = node_runs + .as_array() + .into_iter() + .flatten() + .filter(|row| row["run_id"] == json!(seed_run.run_id)) + .collect(); + assert_eq!( + seeded_node_runs.len(), + usize::from(node_runs.is_array()), + "the seed run built orders once" + ); + for seeded in seeded_node_runs { + for (key, expected) in [ + ("job_key", Value::Null), + ("status", json!("pending")), + ("error_code", Value::Null), + ] { + assert_eq!(seeded[key], expected, "node run {key}"); + } + } + assert_eq!( + node_runs + .as_array() + .into_iter() + .flatten() + .filter(|row| row["job_key"] == json!("orders")) + .count(), + launched_person_runs * usize::from(node_runs.is_array()), + "every person launch of the orders job built orders" + ); for read_path in &unknown_reads { let missing = request( &provider.client, @@ -421,6 +535,24 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ role_case.expected_job ); launched_person_runs += usize::from(role_case.expected_job == 200); + let urgent = json!({"run_id":Uuid::now_v7(), "location_id":location, "target":"development", "job_key":"orders", "reason":"Refresh orders first", "priority":1}); + assert_eq!( + request( + &provider.client, + session, + (Method::POST, "/api/control/runs"), + (&urgent, &session.csrf) + ) + .await + .status() + .as_u16(), + role_case.expected_prioritised, + "a priority above 0 needs run.prioritise" + ); + let accepted = usize::from(role_case.expected_prioritised == 200); + launched_person_runs += accepted; + prioritised_runs + .extend(std::iter::repeat_n(urgent["run_id"].clone(), accepted)); for (endpoint, request_body) in [(&path[..], &body), ("/api/control/runs", &job)] { @@ -466,6 +598,8 @@ fn given_browser_session_roles_when_work_is_requested_then_scope_revocation_and_ } expected_work += usize::from(role_case.expected_connection == 200) * sessions.len(); expected_runs += usize::from(role_case.expected_job == 200) * sessions.len(); + expected_runs += + usize::from(role_case.expected_prioritised == 200) * sessions.len(); assert_eq!( queue_counts(store).await, json!({"work":expected_work,"runs":expected_runs,"claims":0}) diff --git a/crates/sqlbuild-orchestration/tests/api/test_publish_encoding.rs b/crates/sqlbuild-orchestration/tests/api/test_publish_encoding.rs new file mode 100644 index 0000000000..9b2aefbcfc --- /dev/null +++ b/crates/sqlbuild-orchestration/tests/api/test_publish_encoding.rs @@ -0,0 +1,222 @@ +use std::sync::Arc; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use axum::http::HeaderMap; +use axum::routing::post; +use axum::{Json, Router}; +use chrono::Utc; +use reqwest::StatusCode; +use uuid::Uuid; + +use crate::api::constants::BODY_BYTES; +use crate::api::errors::ClientError; +use crate::api::tests::helpers::{ + gzip_bomb, gzipped_empty_object, large_snapshot, not_gzip, oversized_json, start_api, stop_api, +}; +use crate::api::tests::test_types::{OlderServerTestCase, PublishEncodingTestCase}; +use crate::credentials::main::tokens::issue_token::issue_token; +use crate::credentials::models::{AuditContext, CredentialScope}; +use crate::credentials::tests::location_helpers::create_location; +use crate::runs::tests::helpers::snapshot; +use crate::snapshots::main::publish_remote_snapshot::publish_remote_snapshot; +use crate::snapshots::models::{SnapshotInput, SnapshotRecord}; +use crate::store::main::migrate_store::migrate_store; +use crate::store::tests::store_helpers::{open_test_store, run}; +use crate::store::tests::store_test_types::Backend; + +/// Decoding happens before the store is touched, so one backend covers every encoding case. +#[test] +fn given_encoded_snapshot_bodies_when_publishing_then_gzip_is_bounded_after_authentication() { + let test_cases = [ + PublishEncodingTestCase { + description: "an unauthenticated gzip bomb is refused before it is decompressed", + forged_bearer: Some("unknown.credential"), + encoding: "gzip", + body: gzip_bomb, + expected_status: StatusCode::UNAUTHORIZED, + }, + PublishEncodingTestCase { + description: "a gzip body that decompresses past the bound is refused", + forged_bearer: None, + encoding: "gzip", + body: gzip_bomb, + expected_status: StatusCode::PAYLOAD_TOO_LARGE, + }, + PublishEncodingTestCase { + description: "a plain body over the wire bound is refused", + forged_bearer: None, + encoding: "identity", + body: oversized_json, + expected_status: StatusCode::PAYLOAD_TOO_LARGE, + }, + PublishEncodingTestCase { + description: "an encoding other than gzip is refused", + forged_bearer: None, + encoding: "br", + body: gzipped_empty_object, + expected_status: StatusCode::UNSUPPORTED_MEDIA_TYPE, + }, + PublishEncodingTestCase { + description: "a body declared gzip that is not gzip is refused", + forged_bearer: None, + encoding: "gzip", + body: not_gzip, + expected_status: StatusCode::BAD_REQUEST, + }, + ]; + run(async { + let Some(fixture) = open_test_store(Backend::Sqlite).await else { + return; + }; + let store = &fixture.store; + migrate_store(store).await.expect("migrate"); + let location_id = create_location(store, "orders", "test-admin") + .await + .expect("location"); + let audit = AuditContext { + actor: "test-admin".into(), + granting_role: "admin".into(), + request_id: Uuid::now_v7(), + ip_address: None, + }; + let token = issue_token( + store, + &audit, + CredentialScope::Location { location_id }, + Utc::now() + chrono::Duration::hours(1), + ) + .await + .expect("location credential"); + let api = start_api(store).await; + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(120)) + .build() + .expect("HTTP client"); + let path = format!( + "{}/api/locations/{location_id}/targets/development/snapshots", + api.url + ); + for test_case in &test_cases { + let bearer: &str = test_case.forged_bearer.unwrap_or(&token.bearer); + let status = client + .post(&path) + .header("Content-Type", "application/json") + .header("Content-Encoding", test_case.encoding) + .bearer_auth(bearer) + .body((test_case.body)()) + .send() + .await + .expect("a response, not a reset connection") + .status(); + assert_eq!( + status, test_case.expected_status, + "{}", + test_case.description + ); + } + let input = large_snapshot(location_id); + assert!( + serde_json::to_vec(&input).expect("JSON").len() > BODY_BYTES, + "the snapshot only fits the wire bound gzip-encoded" + ); + let published = publish_remote_snapshot(&api.url, &token.bearer, &input) + .await + .expect("publish a 10k-node snapshot"); + assert_eq!(published.generation, 1); + let invalid = SnapshotInput { + generation: 0, + ..input + }; + let refused = publish_remote_snapshot(&api.url, &token.bearer, &invalid).await; + assert!( + matches!(refused, Err(ClientError::Rejected(StatusCode::BAD_REQUEST))), + "a snapshot too large to resend plain reports the server's own 400, got {refused:?}" + ); + let current: serde_json::Value = client + .get(&path) + .bearer_auth(&token.bearer) + .send() + .await + .expect("current snapshot") + .json() + .await + .expect("snapshot JSON"); + assert_eq!( + current["nodes"].as_object().map(serde_json::Map::len), + Some(10_282) + ); + stop_api(api).await; + }); +} + +#[test] +fn given_a_server_that_refuses_gzip_when_publishing_then_the_snapshot_is_resent_as_plain_json() { + let test_cases = [ + OlderServerTestCase { + description: "a server that predates gzip rejects the encoded body as malformed JSON", + refusal: StatusCode::BAD_REQUEST, + expected_requests: 2, + }, + OlderServerTestCase { + description: "a server that refuses the encoding", + refusal: StatusCode::UNSUPPORTED_MEDIA_TYPE, + expected_requests: 2, + }, + ]; + for test_case in test_cases { + run(async { + let requests = Arc::new(AtomicUsize::new(0)); + let seen = Arc::clone(&requests); + let refusal = test_case.refusal; + let router = Router::new().route( + "/api/locations/{location_id}/targets/{target}/snapshots", + post(move |headers: HeaderMap, body: axum::body::Bytes| { + let seen = Arc::clone(&seen); + async move { + seen.fetch_add(1, Ordering::SeqCst); + headers + .get("content-encoding") + .map_or(Ok(()), |_| Err(refusal))?; + let input: SnapshotInput = + serde_json::from_slice(&body).map_err(|_| StatusCode::BAD_REQUEST)?; + Ok::<_, StatusCode>(Json(SnapshotRecord { + snapshot_id: Uuid::nil(), + location_id: input.location_id, + target: input.target, + generation: input.generation, + manifest_digest: "older-server".into(), + published_at: Utc::now(), + })) + } + }), + ); + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .expect("older server socket"); + let url = format!("http://{}", listener.local_addr().expect("address")); + let server = tokio::spawn(async move { axum::serve(listener, router).await }); + let location_id = Uuid::now_v7(); + let published = publish_remote_snapshot(&url, "token.secret", &snapshot(location_id)) + .await + .expect("plain JSON publish to an older server"); + assert_eq!( + published.manifest_digest, "older-server", + "{}", + test_case.description + ); + assert_eq!( + published.location_id, location_id, + "{}", + test_case.description + ); + assert_eq!( + requests.load(Ordering::SeqCst), + test_case.expected_requests, + "{}", + test_case.description + ); + server.abort(); + }); + } +} diff --git a/crates/sqlbuild-orchestration/tests/api/test_reads.rs b/crates/sqlbuild-orchestration/tests/api/test_reads.rs index 52ed1644f1..d23c33b8ed 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_reads.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_reads.rs @@ -3,8 +3,16 @@ use crate::authorization::main::assign_builtin_role::assign_builtin_role; use crate::authorization::main::bootstrap_user::bootstrap_user; use crate::credentials::main::identities::create_principal::create_principal; -use crate::scheduler::tests::contracts::ticks::helpers::calendar_text; +use crate::declarations::main::list_person_triggers::list_person_triggers; +use crate::scheduler::tests::contracts::ticks::helpers::{calendar_text, calendar_time}; use crate::snapshots::main::list_person_snapshots::list_person_snapshots; +use crate::store::errors::ControlError; +use crate::store::main::{begin::begin, commit::commit, query::query}; +use crate::store::models::{Parameter, TransactionMode}; +use crate::warehouse_history::main::read_person_run_events::read_person_run_events; +use crate::warehouse_history::main::read_person_run_output::read_person_run_output; +use crate::warehouse_history::main::read_person_run_statement_sql::read_person_run_statement_sql; +use crate::warehouse_history::main::read_person_run_statements::read_person_run_statements; use std::collections::BTreeMap; use std::time::Duration; @@ -49,13 +57,13 @@ fn given_published_locations_and_scheduled_work_when_read_through_http_then_reso description: "SQLite scoped HTTP reads", backend: Backend::Sqlite, expected_runs: 3, - expected_ticks: 1, + expected_ticks: 2, }, ApiReadTestCase { description: "PostgreSQL scoped HTTP reads", backend: Backend::Postgres, expected_runs: 3, - expected_ticks: 1, + expected_ticks: 2, }, ]; for test_case in test_cases { @@ -322,6 +330,19 @@ fn given_published_locations_and_scheduled_work_when_read_through_http_then_reso let events = read_json(&client, (&event_path, &token.bearer)).await; assert_eq!(events.as_array().expect("events").len(), 1); assert_eq!(events[0]["request_json"], json!(change)); + let (transaction, _) = query( + begin(store, TransactionMode::Write).await.expect("seed older tick"), + "INSERT INTO sqb_ticks (tick_id,trigger_id,scheduled_for,evaluated_at,epoch,outcome) \ + VALUES ($1,$2,$3,$3,1,'covered')", + vec![ + Parameter::Id(Uuid::now_v7()), + Parameter::Id(trigger_id), + Parameter::Time(calendar_time(1)), + ], + ) + .await + .expect("older tick"); + commit(transaction).await.expect("commit older tick"); let ticks = read_json(&client, (&tick_path, &token.bearer)).await; assert_eq!( ticks.as_array().expect("ticks").len(), @@ -329,9 +350,29 @@ fn given_published_locations_and_scheduled_work_when_read_through_http_then_reso ); assert_eq!(ticks[0]["outcome"], "requested"); assert_eq!(ticks[0]["scheduled_for"], calendar_text(2)); + assert_eq!(ticks[1]["outcome"], "covered"); let administrator = bootstrap_user(store, &audit, "orders-admin") .await .expect("administrator"); + for (target, expected) in [ + ("development", ticks[0].clone()), + ("validation", json!(null)), + ] { + let listed = list_person_triggers( + store, + (&audit, administrator), + (location_id, target), + DefaultRole::None, + ) + .await + .expect("person triggers"); + assert_eq!(listed.len(), 1, "{target}"); + assert_eq!( + json!(listed[0].latest_tick), + expected, + "newest tick only, never another target's" + ); + } let mut extra = input.clone(); extra.generation += 1; extra.nodes.remove("products"); @@ -396,6 +437,172 @@ fn given_published_locations_and_scheduled_work_when_read_through_http_then_reso {"change_kind": "added", "subject_key": "role:"}]), "a viewer sees that a role changed, not which" ); + let run = (location_id, "development", claim.request.run_id); + let execution = format!("worker/{}/execution", claim.claim_id); + let run_events = read_person_run_events( + store, + (&audit, viewer), + (run, (0, 1000)), + DefaultRole::None, + ) + .await + .expect("viewer events") + .expect("claimed run"); + assert!( + run_events + .iter() + .any(|event| event.event_type == "run_claimed"), + "scheduler invocation" + ); + assert!( + run_events + .iter() + .any(|event| event.invocation_id == execution + && event.duration_ms == Some(12.75)), + "nested worker invocation" + ); + assert!( + run_events + .iter() + .all(|event| !event.invocation_id.contains("-other")), + "a neighboring claim prefix is not this run" + ); + assert!( + run_events + .windows(2) + .all(|pair| pair[0].storage_id < pair[1].storage_id) + ); + let page = read_person_run_events( + store, + (&audit, viewer), + (run, (run_events[0].storage_id, 1)), + DefaultRole::None, + ) + .await + .expect("event page") + .expect("claimed run"); + assert_eq!( + page.iter() + .map(|event| event.storage_id) + .collect::>(), + [run_events[1].storage_id], + "the cursor resumes after the last event" + ); + let mut statements: Vec<(String, Option)> = + read_person_run_statements(store, (&audit, viewer), (run, 100), DefaultRole::None) + .await + .expect("viewer statements") + .expect("claimed run") + .into_iter() + .map(|statement| (statement.invocation_id, statement.duration_ms)) + .collect(); + statements.sort_by(|left, right| left.0.cmp(&right.0)); + assert_eq!( + statements, + [ + ( + format!("worker/{}/another-invocation", claim.claim_id), + Some(13.25) + ), + (execution.clone(), Some(12.75)), + ] + ); + let statement_id = read_person_run_statements( + store, + (&audit, administrator), + (run, 100), + DefaultRole::None, + ) + .await + .expect("statements") + .expect("claimed run") + .into_iter() + .find(|statement| statement.invocation_id == execution) + .expect("execution statement") + .statement_id; + for refused in [ + read_person_run_statement_sql( + store, + (&audit, viewer), + (run, &statement_id), + DefaultRole::None, + ) + .await + .map(|_| ()), + read_person_run_output(store, (&audit, viewer), run, DefaultRole::None) + .await + .map(|_| ()), + ] { + assert!( + matches!(refused, Err(ControlError::Denied { .. })), + "SQL and output need view.sql and view.evidence, which a viewer lacks" + ); + } + let sql = read_person_run_statement_sql( + store, + (&audit, administrator), + (run, &statement_id), + DefaultRole::None, + ) + .await + .expect("statement SQL") + .expect("the run's own statement"); + assert_eq!( + (sql.statement_id.as_str(), sql.sql), + (statement_id.as_str(), None) + ); + let output = + read_person_run_output(store, (&audit, administrator), run, DefaultRole::None) + .await + .expect("run output") + .expect("claimed run"); + let own = format!("worker/{}", claim.claim_id); + assert!( + output.lines.iter().all(|line| line.invocation_id == own + || line.invocation_id.starts_with(&format!("{own}/"))), + "only this run's worker invocations" + ); + assert_eq!( + output + .lines + .iter() + .filter(|line| (line.stream.as_str(), line.text.as_str()) + == ("stdout", "orders worker output")) + .count(), + 1, + "the neighboring claim prefix's identical line is left out" + ); + let elsewhere = (location_id, "development", foreign_run.run_id); + assert!( + read_person_run_events( + store, + (&audit, administrator), + (elsewhere, (0, 10)), + DefaultRole::None + ) + .await + .expect("foreign run") + .is_none() + && read_person_run_output( + store, + (&audit, administrator), + elsewhere, + DefaultRole::None + ) + .await + .expect("foreign run output") + .is_none() + && read_person_run_statement_sql( + store, + (&audit, administrator), + ((location_id, "development", manual_ids[0]), &statement_id), + DefaultRole::None, + ) + .await + .expect("another run's statement") + .is_none(), + "another location's run and another run's statement are not found" + ); assert!(!history[1].changes.is_empty()); assert_eq!( (history[0].git_dirty, history[1].git_dirty), diff --git a/crates/sqlbuild-orchestration/tests/api/test_types.rs b/crates/sqlbuild-orchestration/tests/api/test_types.rs index 5d3d2b50f3..7f73e0b6c4 100644 --- a/crates/sqlbuild-orchestration/tests/api/test_types.rs +++ b/crates/sqlbuild-orchestration/tests/api/test_types.rs @@ -8,6 +8,20 @@ pub(crate) struct ApiContractTestCase { pub(crate) expected_status: RunStatus, } +pub(crate) struct PublishEncodingTestCase { + pub(crate) description: &'static str, + pub(crate) forged_bearer: Option<&'static str>, + pub(crate) encoding: &'static str, + pub(crate) body: fn() -> Vec, + pub(crate) expected_status: reqwest::StatusCode, +} + +pub(crate) struct OlderServerTestCase { + pub(crate) description: &'static str, + pub(crate) refusal: reqwest::StatusCode, + pub(crate) expected_requests: usize, +} + pub(crate) struct DevelopmentTestCase { pub(crate) description: &'static str, pub(crate) backend: Backend, diff --git a/crates/sqlbuild-orchestration/tests/credentials/test_audit.rs b/crates/sqlbuild-orchestration/tests/credentials/test_audit.rs index 247721fd30..b101ac85c7 100644 --- a/crates/sqlbuild-orchestration/tests/credentials/test_audit.rs +++ b/crates/sqlbuild-orchestration/tests/credentials/test_audit.rs @@ -113,16 +113,39 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ .expect("role assignment") ); assert!(matches!( - read_security_audit(&replica, (&context, ordinary_credentials), (0, 100)).await, + read_security_audit( + &replica, + (&context, ordinary_credentials), + ((0, None), 100) + ) + .await, Err(ControlError::Denied { .. }) )); } - let existing = read_security_audit(store, (&context, admin_credentials), (0, 100)) - .await - .expect("existing security facts"); - let upgraded = read_security_audit(&replica, (&context, admin_credentials), (0, 100)) - .await - .expect("replica audit"); + let existing = + read_security_audit(store, (&context, admin_credentials), ((0, None), 100)) + .await + .expect("existing security facts"); + assert_eq!( + existing + .iter() + .filter(|event| event.actor == format!("principal:{admin}")) + .map(|event| event.actor_name.as_deref()) + .collect::>(), + [Some("orders-admin")].into(), + "a principal actor is named" + ); + assert!( + existing + .iter() + .filter(|event| !event.actor.starts_with("principal:")) + .all(|event| event.actor_name.is_none()), + "other actors keep only their actor text" + ); + let upgraded = + read_security_audit(&replica, (&context, admin_credentials), ((0, None), 100)) + .await + .expect("replica audit"); assert_eq!( upgraded .iter() @@ -180,9 +203,10 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ .expect("durable clock fact"); commit(transaction).await.expect("commit clock fact"); } - let first = read_security_audit(&replica, (&context, admin_credentials), (cursor, 2)) - .await - .expect("first bounded page"); + let first = + read_security_audit(&replica, (&context, admin_credentials), ((cursor, None), 2)) + .await + .expect("first bounded page"); assert_eq!( first.len(), test_case.expected_page_length, @@ -206,7 +230,7 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ let last = read_security_audit( store, (&context, admin_credentials), - (first[1].storage_id, 2), + ((first[1].storage_id, None), 2), ) .await .expect("strict continuation"); @@ -214,14 +238,29 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ last.iter().map(|event| event.event_id).collect::>(), ids[2..] ); + let newest = read_security_audit( + store, + (&context, admin_credentials), + ((cursor, Some(last[0].storage_id)), 100), + ) + .await + .expect("newest-first window"); + assert_eq!( + newest + .iter() + .map(|event| event.event_id) + .collect::>(), + [ids[1], ids[0]], + "both bounds are exclusive and the window reads newest first" + ); let cursor = last[0].storage_id; assert!( - read_security_audit(store, (&context, admin_credentials), (cursor, 100)) + read_security_audit(store, (&context, admin_credentials), ((cursor, None), 100)) .await .expect("empty continuation") .is_empty() ); - for bounds in [(-1, 1), (0, 0), (0, 101)] { + for bounds in [((-1, None), 1), ((0, None), 0), ((0, None), 101)] { assert!(matches!( read_security_audit(store, (&context, admin_credentials), bounds).await, Err(ControlError::Invalid(_)) @@ -243,17 +282,22 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ ); remove_fault(store, &fault).await; assert!( - read_security_audit(&replica, (&context, admin_credentials), (cursor, 100)) - .await - .expect("failed audit did not publish") - .is_empty() + read_security_audit( + &replica, + (&context, admin_credentials), + ((cursor, None), 100) + ) + .await + .expect("failed audit did not publish") + .is_empty() ); create_principal(store, &context, "orders-rollback") .await .expect("failed principal did not persist"); - let after = read_security_audit(store, (&context, admin_credentials), (cursor, 100)) - .await - .expect("successful retry"); + let after = + read_security_audit(store, (&context, admin_credentials), ((cursor, None), 100)) + .await + .expect("successful retry"); assert_eq!(after.len(), 1); assert_eq!(after[0].storage_id, cursor + 1); let cursor = after[0].storage_id; @@ -266,7 +310,7 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ right.expect("second concurrent append") ); let concurrent = - read_security_audit(store, (&context, admin_credentials), (cursor, 100)) + read_security_audit(store, (&context, admin_credentials), ((cursor, None), 100)) .await .expect("serialized writers"); assert_eq!( @@ -312,11 +356,12 @@ fn given_security_audit_pages_when_clock_and_authority_change_then_commit_order_ .expect("live role revocation") ); assert!(matches!( - read_security_audit(&replica, (&context, admin_credentials), (0, 100)).await, + read_security_audit(&replica, (&context, admin_credentials), ((0, None), 100)) + .await, Err(ControlError::Denied { .. }) )); assert!( - !read_security_audit(&replica, (&context, ordinary_credentials), (0, 100)) + !read_security_audit(&replica, (&context, ordinary_credentials), ((0, None), 100)) .await .expect("replacement administrator reads audit") .is_empty() diff --git a/crates/sqlbuild-orchestration/tests/credentials/test_project_roles.rs b/crates/sqlbuild-orchestration/tests/credentials/test_project_roles.rs index 387778bc48..543013707a 100644 --- a/crates/sqlbuild-orchestration/tests/credentials/test_project_roles.rs +++ b/crates/sqlbuild-orchestration/tests/credentials/test_project_roles.rs @@ -4,7 +4,11 @@ use std::collections::{BTreeMap, BTreeSet}; use serde_json::json; +use crate::api::models::GroupRole; use crate::authorization::main::compile_roles::compile_roles; +use crate::authorization::main::list_role_definitions::list_role_definitions; +use crate::authorization::main::publish_group_roles::publish_group_roles; +use crate::credentials::main::identities::create_principal::create_principal; use crate::credentials::models::{Action, RoleAssignmentRequest, RoleKey}; use crate::credentials::tests::role_helpers::{ SUPPORT_ROLE, assign, authorize_member, developer_grants, member_listings, @@ -15,7 +19,10 @@ use crate::credentials::tests::test_types::{ProjectRoleTestCase, RolesFileTestCa use crate::declarations::models::TriggerChange; use crate::runs::tests::helpers::snapshot; use crate::snapshots::main::publish_snapshot::publish_snapshot; +use crate::store::errors::ControlError; use crate::store::main::migrate_store::migrate_store; +use crate::store::main::{begin::begin, commit::commit, query::query}; +use crate::store::models::{Parameter, TransactionMode}; use crate::store::tests::store_helpers::{close_test_store, open_test_store, run}; use crate::store::tests::store_test_types::Backend; @@ -91,6 +98,88 @@ fn given_published_selector_when_it_matches_more_nodes_then_coverage_grows_on_pu "{}: the new role, the dropped view grant and the grown coverage enter the change feed", test_case.description ); + publish_group_roles( + store, + &[GroupRole { + group: "support-team".into(), + role: RoleKey::Custom(SUPPORT_ROLE.into()), + location_id: Some(roles.location_id), + target: None, + }], + ) + .await + .expect("group mapping"); + let grouped = create_principal(store, &roles.context, "support-lead") + .await + .expect("group member"); + for principal in [grouped, roles.member_id] { + let transaction = begin(store, TransactionMode::Write) + .await + .expect("group membership"); + let (transaction, _) = query( + transaction, + "INSERT INTO sqb_person_groups (principal_id,group_name) VALUES ($1,$2)", + vec![ + Parameter::Id(principal), + Parameter::Text("support-team".into()), + ], + ) + .await + .expect("member of the group"); + commit(transaction).await.expect("membership committed"); + } + let definitions = list_role_definitions( + store, + (&roles.context, roles.administrator.identity.principal_id), + (roles.location_id, "development"), + ) + .await + .expect("role definitions"); + assert_eq!( + definitions + .iter() + .map(|role| (role.key.as_str(), role.builtin, role.holders)) + .collect::>(), + [ + ("viewer", true, 0), + ("operator", true, 0), + ("developer", true, 0), + ("admin", true, 1), + (SUPPORT_ROLE, false, 2) + ], + "{}: built-in roles, then the published role, each counting a person once whether assigned directly or through a group", + test_case.description + ); + assert_eq!( + definitions[0].grants[0].permissions, + [Action::View], + "a viewer views every node" + ); + let support = &definitions[4]; + assert_eq!( + support.description.as_deref(), + Some("Answer order questions") + ); + assert_eq!( + support.grants[0].matched, + BTreeMap::from([( + "tag:orders".to_owned(), + vec!["customers".to_owned(), "orders".to_owned()] + )]), + "each selector shows the nodes it matched at the latest publish" + ); + assert!( + matches!( + list_role_definitions( + store, + (&roles.context, roles.member_id), + (roles.location_id, "development"), + ) + .await, + Err(ControlError::Denied { .. }) + ), + "people without role.assign everywhere cannot read role definitions" + ); close_test_store(fixture).await; }); } diff --git a/crates/sqlbuild-orchestration/tests/platform/test_contracts.rs b/crates/sqlbuild-orchestration/tests/platform/test_contracts.rs index 0f5c53a679..eb9b98bffe 100644 --- a/crates/sqlbuild-orchestration/tests/platform/test_contracts.rs +++ b/crates/sqlbuild-orchestration/tests/platform/test_contracts.rs @@ -2,6 +2,8 @@ #[path = "helpers.rs"] mod helpers; +#[path = "test_problems.rs"] +mod test_problems; #[path = "test_types.rs"] mod test_types; #[path = "test_watchdog.rs"] diff --git a/crates/sqlbuild-orchestration/tests/platform/test_problems.rs b/crates/sqlbuild-orchestration/tests/platform/test_problems.rs new file mode 100644 index 0000000000..3ebb270633 --- /dev/null +++ b/crates/sqlbuild-orchestration/tests/platform/test_problems.rs @@ -0,0 +1,167 @@ +use crate::authorization::main::{ + assign_builtin_role::assign_builtin_role, bootstrap_user::bootstrap_user, +}; +use crate::credentials::main::identities::create_principal::create_principal; +use crate::credentials::models::{AuditContext, BuiltinRole, DefaultRole, RoleAssignmentRequest}; +use crate::platform::main::read_person_problems::read_person_problems; +use crate::platform::main::report_location_health::report_location_health; +use crate::platform::models::{LocationHealth, LocationHealthReport}; +use crate::scheduler::tests::contracts::helpers::acquire_lease; +use crate::snapshots::main::publish_snapshot::publish_snapshot; +use crate::store::main::begin::begin; +use crate::store::main::commit::commit; +use crate::store::main::migrate_store::migrate_store; +use crate::store::main::query::query; +use crate::store::models::{Parameter, TransactionMode}; +use chrono::TimeDelta; +use uuid::Uuid; + +use crate::credentials::tests::location_helpers::create_location; +use crate::platform::tests::helpers::snapshot; +use crate::platform::tests::test_types::ProblemsTestCase; +use crate::store::tests::store_helpers::{close_test_store, open_test_store, run}; +use crate::store::tests::store_test_types::Backend; + +#[test] +fn given_broken_locations_and_late_ticks_when_people_read_problems_then_only_listed_targets_appear() +{ + let test_cases = [ + ProblemsTestCase { + description: "SQLite without a leader", + backend: Backend::Sqlite, + leaders: 0, + expected_leader: false, + }, + ProblemsTestCase { + description: "PostgreSQL with a leader", + backend: Backend::Postgres, + leaders: 1, + expected_leader: true, + }, + ]; + for test_case in test_cases { + run(async { + let Some(fixture) = open_test_store(test_case.backend).await else { + return; + }; + let store = &fixture.store; + migrate_store(store).await.expect("migrate problems store"); + let context = AuditContext { + actor: "control-store-owner".into(), + granting_role: "control-store-owner".into(), + request_id: Uuid::now_v7(), + ip_address: None, + }; + let admin = bootstrap_user(store, &context, "orders-admin") + .await + .expect("administrator"); + let developer = create_principal(store, &context, "orders-developer") + .await + .expect("developer"); + let outsider = create_principal(store, &context, "orders-outsider") + .await + .expect("outsider"); + let mut locations = Vec::new(); + for name in ["orders", "inventory"] { + let location = create_location(store, name, "test-admin") + .await + .expect("location"); + publish_snapshot(store, &snapshot(location)) + .await + .expect("published triggers"); + report_location_health( + store, + location, + &LocationHealthReport { + event_id: Uuid::now_v7(), + session_id: Uuid::now_v7(), + target: "development".into(), + health: LocationHealth::Heartbeat {}, + }, + ) + .await + .expect("instance check-in"); + locations.push(location); + } + let transaction = begin(store, TransactionMode::Write) + .await + .expect("age check-ins"); + let stale = transaction.now - TimeDelta::minutes(5); + let (transaction, _) = query( + transaction, + "UPDATE sqb_location_instances SET last_seen_at=$1", + vec![Parameter::Time(stale)], + ) + .await + .expect("stale check-ins"); + commit(transaction).await.expect("aged check-ins"); + assign_builtin_role( + store, + (&context, admin), + &RoleAssignmentRequest { + principal_id: developer, + role: BuiltinRole::Developer.into(), + location_id: Some(locations[1]), + target: Some("development".into()), + revoke: false, + }, + ) + .await + .expect("developer on inventory only"); + for _ in 0..test_case.leaders { + acquire_lease(store, Uuid::now_v7()) + .await + .expect("acquire lease") + .expect("leader"); + } + let read = |principal: Uuid| read_person_problems(store, principal, DefaultRole::None); + let everything = read(admin).await.expect("administrator problems"); + assert_eq!( + everything.scheduler_leader, test_case.expected_leader, + "{}", + test_case.description + ); + assert_eq!( + everything.scheduler_lease_expires_at.is_some(), + test_case.expected_leader + ); + assert_eq!( + everything + .broken_locations + .iter() + .map(|location| location.location_name.as_str()) + .collect::>(), + std::collections::BTreeSet::from(["inventory", "orders"]) + ); + assert_eq!( + everything.overdue_ticks.len(), + 200, + "each listed target keeps its own 100 late ticks" + ); + let scoped = read(developer).await.expect("developer problems"); + assert_eq!(scoped.scheduler_leader, everything.scheduler_leader); + assert_eq!( + scoped + .broken_locations + .iter() + .map(|location| location.location_id) + .collect::>(), + vec![locations[1]], + "only the listed location is reported broken" + ); + assert_eq!(scoped.overdue_ticks.len(), 100); + assert!( + scoped + .overdue_ticks + .iter() + .all(|tick| tick.location_id == locations[1] + && tick.location_name == "inventory"), + "another location's late ticks stay hidden" + ); + let hidden = read(outsider).await.expect("outsider problems"); + assert_eq!(hidden.scheduler_leader, everything.scheduler_leader); + assert!(hidden.broken_locations.is_empty() && hidden.overdue_ticks.is_empty()); + close_test_store(fixture).await; + }); + } +} diff --git a/crates/sqlbuild-orchestration/tests/platform/test_types.rs b/crates/sqlbuild-orchestration/tests/platform/test_types.rs index 52ecb92e0c..8fa6dd4a47 100644 --- a/crates/sqlbuild-orchestration/tests/platform/test_types.rs +++ b/crates/sqlbuild-orchestration/tests/platform/test_types.rs @@ -51,3 +51,10 @@ pub(crate) struct WebhookState { pub(crate) status: u16, pub(crate) requests: Vec, } + +pub(crate) struct ProblemsTestCase { + pub(crate) description: &'static str, + pub(crate) backend: Backend, + pub(crate) leaders: usize, + pub(crate) expected_leader: bool, +} diff --git a/crates/sqlbuild-orchestration/tests/runs/helpers.rs b/crates/sqlbuild-orchestration/tests/runs/helpers.rs index 2b27de9ba7..4594bf388d 100644 --- a/crates/sqlbuild-orchestration/tests/runs/helpers.rs +++ b/crates/sqlbuild-orchestration/tests/runs/helpers.rs @@ -2,14 +2,25 @@ use std::collections::{BTreeMap, BTreeSet}; +use crate::api::errors::ApiError; +use crate::authorization::main::assign_builtin_role::assign_builtin_role; +use crate::authorization::main::bootstrap_user::bootstrap_user; +use crate::credentials::main::identities::create_principal::create_principal; +use crate::credentials::models::{AuditContext, BuiltinRole, RoleAssignmentRequest}; +use crate::credentials::tests::location_helpers::create_location; use crate::runs::_helpers::requests::publication::publish_request; -use crate::runs::models::{RunKind, RunOrigin, RunRequest}; +use crate::runs::models::{ + AdhocCheck, AdhocCheckRequest, AdhocRunRequest, RunKind, RunOrigin, RunRequest, +}; +use crate::runs::tests::test_types::{AdhocCommandTestCase, AdhocPeople}; +use crate::snapshots::main::publish_snapshot::publish_snapshot; use crate::snapshots::models::{SnapshotInput, SnapshotNode}; use crate::store::errors::ControlError; use crate::store::main::begin::begin; use crate::store::main::commit::commit; -use crate::store::models::{ControlStore, TransactionMode}; -use serde_json::json; +use crate::store::main::query::query; +use crate::store::models::{ControlStore, Parameter, TransactionMode}; +use serde_json::{Value, json}; use uuid::Uuid; pub(crate) async fn request_run( @@ -102,3 +113,162 @@ pub(crate) async fn queue(store: &ControlStore, request: &RunRequest) { "a reused run ID cannot change its request" ); } + +/// Rows of `sql` as JSON, read in their own transaction. +pub(crate) async fn rows( + store: &ControlStore, + sql: &str, + parameters: Vec, +) -> Vec { + let transaction = begin(store, TransactionMode::Read).await.expect("read"); + let (transaction, rows) = query(transaction, sql, parameters).await.expect("query"); + commit(transaction).await.expect("commit"); + rows.into_iter().map(|row| json!(row)).collect() +} + +/// A JSON column, which SQLite returns as text and PostgreSQL as JSON. +pub(crate) fn json_column(value: &Value) -> Value { + value.as_str().map_or(value.clone(), |text| { + serde_json::from_str(text).expect("JSON column") + }) +} + +/// A publication of two models, a seed and a source, with one person per role under test. +pub(crate) async fn adhoc_fixture( + store: &ControlStore, + context: &AuditContext, +) -> (Uuid, Uuid, AdhocPeople) { + let location_id = create_location(store, "orders", "test-owner") + .await + .expect("location"); + let mut input = snapshot(location_id); + input.nodes = [ + "model:orders", + "model:customers", + "seed:currencies", + "source:shop.orders", + ] + .into_iter() + .map(published_node) + .collect(); + let published = publish_snapshot(store, &input).await.expect("publish"); + let admin = bootstrap_user(store, context, "orders-admin") + .await + .expect("administrator"); + let developer = person_with( + store, + (context, admin), + "orders-developer", + &[BuiltinRole::Developer], + ) + .await; + let operator = person_with( + store, + (context, admin), + "orders-operator", + &[BuiltinRole::Operator], + ) + .await; + let stranger = person_with(store, (context, admin), "orders-stranger", &[]).await; + ( + location_id, + published.snapshot_id, + AdhocPeople { + admin, + developer, + operator, + stranger, + }, + ) +} + +fn published_node(key: &str) -> (String, SnapshotNode) { + let kind = key.split_once(':').expect("kind").0; + ( + key.to_owned(), + SnapshotNode { + kind: kind.into(), + definition: json!({"materialization": "table"}), + raw_sql: None, + compiled_sql: Some("select 1 as id".into()), + python_source: None, + }, + ) +} + +async fn person_with( + store: &ControlStore, + (context, admin): (&AuditContext, Uuid), + name: &str, + roles: &[BuiltinRole], +) -> Uuid { + let person = create_principal(store, context, name) + .await + .expect("person"); + for role in roles.iter().copied() { + assign_builtin_role( + store, + (context, admin), + &RoleAssignmentRequest { + principal_id: person, + role: role.into(), + location_id: None, + target: None, + revoke: false, + }, + ) + .await + .expect("assign role"); + } + person +} + +/// The case's command as a dry run in the fixture's location. +pub(crate) fn adhoc_check( + location_id: Uuid, + test_case: &AdhocCommandTestCase, +) -> AdhocCheckRequest { + AdhocCheckRequest { + location_id, + target: "development".into(), + command: test_case + .command + .iter() + .map(|token| (*token).to_owned()) + .collect(), + priority: test_case.priority, + } +} + +/// The case's command as a launch with its own run ID. +pub(crate) fn adhoc_launch( + location_id: Uuid, + test_case: &AdhocCommandTestCase, + run_id: Uuid, +) -> AdhocRunRequest { + let check = adhoc_check(location_id, test_case); + AdhocRunRequest { + run_id, + location_id, + target: check.target, + command: check.command, + reason: test_case.reason.into(), + priority: test_case.priority, + } +} + +/// What a dry run decided, in the shape the cases name it. +pub(crate) fn check_projection(check: &AdhocCheck) -> Value { + json!({ + "selection": check.selection, + "write_set": check.write_set, + "permissions": check.permissions.iter().map(|permission| json!([permission.action, permission.allowed])).collect::>(), + "error": check.error, + "launchable": check.launchable, + }) +} + +/// Whether a launch applied, or the HTTP status its refusal reaches people with. +pub(crate) fn launch_outcome(result: Result) -> Result { + result.map_err(|error| ApiError::from(error).status.as_u16()) +} diff --git a/crates/sqlbuild-orchestration/tests/runs/test_adhoc.rs b/crates/sqlbuild-orchestration/tests/runs/test_adhoc.rs new file mode 100644 index 0000000000..7cf277f638 --- /dev/null +++ b/crates/sqlbuild-orchestration/tests/runs/test_adhoc.rs @@ -0,0 +1,264 @@ +//! Ad-hoc commands resolve on the server, need their own permission per gated flag and dry runs record nothing. + +use serde_json::{Value, json}; +use uuid::Uuid; + +use crate::credentials::models::AuditContext; +use crate::runs::main::check_adhoc_run::check_adhoc_run; +use crate::runs::main::request_person_run::request_person_run; +use crate::runs::models::PersonRunRequest; +use crate::runs::tests::helpers::{ + adhoc_check, adhoc_fixture, adhoc_launch, check_projection, json_column, launch_outcome, rows, +}; +use crate::runs::tests::test_types::{AdhocCommandTestCase, AdhocPerson, ExactKeyPlanner}; +use crate::store::main::migrate_store::migrate_store; +use crate::store::models::Parameter; +use crate::store::tests::store_helpers::{close_test_store, open_test_store, run}; +use crate::store::tests::store_test_types::Backend; + +const AUDIT: &str = "SELECT event_id FROM sqb_security_audit"; + +#[test] +fn given_ad_hoc_commands_when_people_check_and_launch_them_then_each_gated_flag_needs_its_own_permission() + { + let test_cases = [ + AdhocCommandTestCase { + description: "run.adhoc covers the resolved selection, not only its writers", + person: AdhocPerson::Developer, + command: &["build", "model:orders", "source:shop.orders"], + priority: 0, + reason: "Refresh orders", + expected_check: json!({"selection": ["model:orders", "source:shop.orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true]], "error": null, "launchable": true}), + expected_launch: Ok(true), + }, + AdhocCommandTestCase { + description: "every gated flag is its own permission, and Developer holds none of them", + person: AdhocPerson::Developer, + command: &[ + "build", + "model:orders", + "--full-refresh", + "--warehouse", + "BUILD_XS", + ], + priority: 1, + reason: "Rebuild orders now", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true], ["run.rewrite_history", false], ["run.warehouse", false], ["run.prioritise", false]], + "error": null, "launchable": false}), + expected_launch: Err(403), + }, + AdhocCommandTestCase { + description: "Developer lacks run.rewrite_history alone", + person: AdhocPerson::Developer, + command: &["build", "model:orders", "--full-refresh"], + priority: 0, + reason: "Rebuild orders", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true], ["run.rewrite_history", false]], "error": null, "launchable": false}), + expected_launch: Err(403), + }, + AdhocCommandTestCase { + description: "Developer lacks run.warehouse alone", + person: AdhocPerson::Developer, + command: &["build", "model:orders", "--warehouse", "BUILD_XS"], + priority: 0, + reason: "Refresh orders", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true], ["run.warehouse", false]], "error": null, "launchable": false}), + expected_launch: Err(403), + }, + AdhocCommandTestCase { + description: "Developer lacks run.prioritise alone", + person: AdhocPerson::Developer, + command: &["build", "model:orders"], + priority: 1, + reason: "Refresh orders", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true], ["run.prioritise", false]], "error": null, "launchable": false}), + expected_launch: Err(403), + }, + AdhocCommandTestCase { + description: "Admin holds every gated permission", + person: AdhocPerson::Admin, + command: &[ + "build", + "model:orders", + "--full-refresh", + "--warehouse", + "BUILD_XS", + ], + priority: 1, + reason: "Rebuild orders now", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true], ["run.rewrite_history", true], ["run.warehouse", true], ["run.prioritise", true]], + "error": null, "launchable": true}), + expected_launch: Ok(true), + }, + AdhocCommandTestCase { + description: "Operator may run jobs but not ad-hoc commands", + person: AdhocPerson::Operator, + command: &["build", "model:orders"], + priority: 0, + reason: "Refresh orders", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", false]], "error": null, "launchable": false}), + expected_launch: Err(403), + }, + AdhocCommandTestCase { + description: "without view the dry run reveals no node, and no role is the default", + person: AdhocPerson::Stranger, + command: &["build", "model:orders"], + priority: 0, + reason: "Refresh orders", + expected_check: json!({"selection": [], "write_set": [], "permissions": [], + "error": "view on every node of this target is required", "launchable": false}), + expected_launch: Err(403), + }, + AdhocCommandTestCase { + description: "verbs workers cannot run yet are refused at launch", + person: AdhocPerson::Developer, + command: &["test", "model:orders"], + priority: 0, + reason: "Check orders", + expected_check: json!({"selection": ["model:orders"], "write_set": [], "permissions": [["run.adhoc", true]], + "error": "workers do not run sqb test yet; only build can launch", "launchable": false}), + expected_launch: Err(400), + }, + AdhocCommandTestCase { + description: "a refused flag is refused before any permission", + person: AdhocPerson::Admin, + command: &["build", "--vars"], + priority: 0, + reason: "Refresh orders", + expected_check: json!({"selection": [], "write_set": [], "permissions": [], + "error": "--vars is not available for ad-hoc runs", "launchable": false}), + expected_launch: Err(400), + }, + AdhocCommandTestCase { + description: "a launch needs a reason, which the dry run does not", + person: AdhocPerson::Admin, + command: &["build", "model:orders"], + priority: 0, + reason: " ", + expected_check: json!({"selection": ["model:orders"], "write_set": ["model:orders"], + "permissions": [["run.adhoc", true]], "error": null, "launchable": true}), + expected_launch: Err(400), + }, + ]; + for backend in [Backend::Sqlite, Backend::Postgres] { + run(async { + let Some(fixture) = open_test_store(backend).await else { + return; + }; + let store = &fixture.store; + migrate_store(store).await.expect("migrate"); + let context = AuditContext { + actor: "local-control-store-owner".into(), + granting_role: "control-store-owner".into(), + request_id: Uuid::now_v7(), + ip_address: None, + }; + let (location_id, snapshot_id, people) = adhoc_fixture(store, &context).await; + let mut run_ids: Vec = Vec::new(); + for test_case in &test_cases { + let run_id = Uuid::now_v7(); + run_ids.push(run_id); + let person = people.of(test_case.person); + let audited = rows(store, AUDIT, Vec::new()).await.len(); + let check = check_adhoc_run( + store, + (&context, person), + &ExactKeyPlanner, + &adhoc_check(location_id, test_case), + ) + .await + .expect("dry run"); + assert_eq!( + ( + check_projection(&check), + rows(store, AUDIT, Vec::new()).await.len() + ), + (test_case.expected_check.clone(), audited), + "{backend:?}: {}; a dry run records nothing", + test_case.description + ); + let launched = request_person_run( + store, + (&context, person), + PersonRunRequest::Adhoc( + &ExactKeyPlanner, + &adhoc_launch(location_id, test_case, run_id), + ), + ) + .await; + assert_eq!( + launch_outcome(launched), + test_case.expected_launch, + "{backend:?}: {}", + test_case.description + ); + } + let retried = request_person_run( + store, + (&context, people.of(test_cases[0].person)), + PersonRunRequest::Adhoc( + &ExactKeyPlanner, + &adhoc_launch(location_id, &test_cases[0], run_ids[0]), + ), + ) + .await; + assert_eq!( + launch_outcome(retried), + Ok(false), + "{backend:?}: a retry of the same run ID is not a second run" + ); + let frozen = rows( + store, + "SELECT request_json FROM sqb_runs WHERE run_id=$1", + vec![Parameter::Id(run_ids[0])], + ) + .await; + let frozen = json_column(&frozen[0]["request_json"]); + assert_eq!( + [ + &frozen["kind"], + &frozen["selection"], + &frozen["write_set"], + &frozen["snapshot_id"] + ], + [ + &json!("adhoc"), + &json!(["model:orders", "source:shop.orders"]), + &json!(["model:orders"]), + &json!(snapshot_id) + ], + "{backend:?}: the worker receives the exact resolved selection of the pinned publication" + ); + let audit = rows( + store, + "SELECT after_json FROM sqb_security_audit WHERE action='run.adhoc_requested'", + Vec::new(), + ) + .await; + assert_eq!( + audit + .iter() + .map(|row| json_column(&row["after_json"])) + .map(|event| json!([ + event["verb"], + event["frozen"]["settings"], + event["request"]["reason"] + ])) + .collect::>(), + vec![ + json!(["build", {}, "Refresh orders"]), + json!(["build", {"full_refresh": true, "warehouse": "BUILD_XS"}, "Rebuild orders now"]), + ], + "{backend:?}: each launch is audited once with its command, reason and warehouse" + ); + close_test_store(fixture).await; + }); + } +} diff --git a/crates/sqlbuild-orchestration/tests/runs/test_contracts.rs b/crates/sqlbuild-orchestration/tests/runs/test_contracts.rs index ddc82ea434..0c5abb3f2d 100644 --- a/crates/sqlbuild-orchestration/tests/runs/test_contracts.rs +++ b/crates/sqlbuild-orchestration/tests/runs/test_contracts.rs @@ -6,6 +6,8 @@ pub(crate) mod helpers; mod monitor_helpers; #[path = "termination/helpers.rs"] pub(crate) mod terminal_helpers; +#[path = "test_adhoc.rs"] +mod test_adhoc; #[path = "test_checks.rs"] mod test_checks; #[path = "test_claims.rs"] @@ -29,4 +31,4 @@ mod test_requests; #[path = "test_reruns.rs"] mod test_reruns; #[path = "test_types.rs"] -mod test_types; +pub(crate) mod test_types; diff --git a/crates/sqlbuild-orchestration/tests/runs/test_permissions.rs b/crates/sqlbuild-orchestration/tests/runs/test_permissions.rs index 131c80a45a..f6d4c726fb 100644 --- a/crates/sqlbuild-orchestration/tests/runs/test_permissions.rs +++ b/crates/sqlbuild-orchestration/tests/runs/test_permissions.rs @@ -17,12 +17,13 @@ use crate::credentials::models::{ }; use crate::credentials::tests::location_helpers::create_location; use crate::runs::main::{ - advance_groups::advance_groups, cancel_person_run::cancel_person_run, change_run::change_run, - list_person_runs::list_person_runs, list_runs::list_runs, request_job_run::request_job_run, - request_person_job_run::request_person_job_run, request_rerun::request_rerun, + advance_groups::advance_groups, change_run::change_run, list_person_runs::list_person_runs, + list_runs::list_runs, request_job_run::request_job_run, request_person_run::request_person_run, + request_rerun::request_rerun, }; use crate::runs::models::{ - CancelRunRequest, JobRunRequest, RerunRequest, RunAuthority, RunChange, RunChangeRequest, + CancelRunRequest, JobRunRequest, PersonRunRequest, RerunRequest, RunAuthority, RunChange, + RunChangeRequest, }; use crate::runs::tests::helpers::{request_run, snapshot}; use crate::runs::tests::test_types::PersonRunPermissionTestCase; @@ -142,7 +143,12 @@ fn given_scoped_person_roles_when_jobs_request_sensitive_settings_then_only_admi }; assert!( matches!( - request_person_job_run(store, (&context, credentials), &request).await, + request_person_run( + store, + (&context, credentials), + PersonRunRequest::Job(&request) + ) + .await, Err(ControlError::Denied { .. }) ), "no role is the default" @@ -180,14 +186,14 @@ fn given_scoped_person_roles_when_jobs_request_sensitive_settings_then_only_admi .expect("leader"); let mut reruns = Vec::new(); for job_key in ["daily_orders", "orders_group"] { - let result = request_person_job_run( + let result = request_person_run( store, (&context, credentials), - &JobRunRequest { + PersonRunRequest::Job(&JobRunRequest { run_id: Uuid::now_v7(), job_key: job_key.into(), ..request.clone() - }, + }), ) .await; assert_eq!( @@ -209,11 +215,12 @@ fn given_scoped_person_roles_when_jobs_request_sensitive_settings_then_only_admi &JobRunRequest { run_id: source_id, job_key: job_key.into(), + priority: 1, ..request.clone() }, ) .await - .expect("owner creates source with frozen settings"); + .expect("owner creates urgent source with frozen settings"); let cancellation = CancelRunRequest { run_id: source_id, event_id: Uuid::now_v7(), @@ -222,11 +229,15 @@ fn given_scoped_person_roles_when_jobs_request_sensitive_settings_then_only_admi let can_cancel = test_case.role != BuiltinRole::Viewer; for expected in [true, false] { assert_eq!( - cancel_person_run(store, (&context, credentials), &cancellation) - .await - .ok(), + request_person_run( + store, + (&context, credentials), + PersonRunRequest::Cancel(&cancellation), + ) + .await + .ok(), can_cancel.then_some(expected), - "cancellation permits stopping even sensitive work" + "cancellation permits stopping even sensitive or prioritised work" ); } let source_id = Uuid::now_v7(); @@ -301,14 +312,14 @@ fn given_scoped_person_roles_when_jobs_request_sensitive_settings_then_only_admi request_run(store, &adhoc) .await .expect("owner requests ad-hoc execution"); - cancel_person_run( + request_person_run( store, (&context, sessions[0].identity.principal_id), - &CancelRunRequest { + PersonRunRequest::Cancel(&CancelRunRequest { run_id: adhoc.run_id, event_id: Uuid::now_v7(), reason: "Stop ad-hoc source".into(), - }, + }), ) .await .expect("administrator stops ad-hoc execution"); @@ -375,13 +386,13 @@ fn given_scoped_person_roles_when_jobs_request_sensitive_settings_then_only_admi ); assert!( matches!( - request_person_job_run( + request_person_run( store, (&context, credentials), - &JobRunRequest { + PersonRunRequest::Job(&JobRunRequest { run_id: Uuid::now_v7(), ..request - } + }) ) .await, Err(ControlError::Denied { .. }) diff --git a/crates/sqlbuild-orchestration/tests/runs/test_types.rs b/crates/sqlbuild-orchestration/tests/runs/test_types.rs index 0214432a6d..0f4cc521f6 100644 --- a/crates/sqlbuild-orchestration/tests/runs/test_types.rs +++ b/crates/sqlbuild-orchestration/tests/runs/test_types.rs @@ -2,6 +2,8 @@ use std::collections::BTreeSet; +use uuid::Uuid; + use crate::credentials::models::BuiltinRole; use crate::runs::models::{RerunRequest, RunStatus}; @@ -114,3 +116,78 @@ pub(crate) struct MixedAdmissionTestCase { pub(crate) parent_priority: i64, pub(crate) expected_selections: [&'static str; 2], } + +/// Who runs an ad-hoc case: Admin, Developer, Operator, or a person with no role. +#[derive(Clone, Copy)] +pub(crate) enum AdhocPerson { + Admin, + Developer, + Operator, + Stranger, +} + +/// The people the ad-hoc fixture provisions. +pub(crate) struct AdhocPeople { + pub(crate) admin: Uuid, + pub(crate) developer: Uuid, + pub(crate) operator: Uuid, + pub(crate) stranger: Uuid, +} + +impl AdhocPeople { + pub(crate) fn of(&self, person: AdhocPerson) -> Uuid { + match person { + AdhocPerson::Admin => self.admin, + AdhocPerson::Developer => self.developer, + AdhocPerson::Operator => self.operator, + AdhocPerson::Stranger => self.stranger, + } + } +} + +/// One person's command: what its dry run decides and what launching it does. +pub(crate) struct AdhocCommandTestCase { + pub(crate) description: &'static str, + pub(crate) person: AdhocPerson, + pub(crate) command: &'static [&'static str], + pub(crate) priority: i64, + pub(crate) reason: &'static str, + pub(crate) expected_check: serde_json::Value, + pub(crate) expected_launch: Result, +} + +/// The CLI's planner reduced to exact node keys, `--full-refresh` and `--warehouse NAME`. +pub(crate) struct ExactKeyPlanner; + +impl crate::runs::types::CommandPlanner for ExactKeyPlanner { + fn plan( + &self, + command: &[String], + resources: &[crate::runs::models::PlannerResource], + ) -> Result { + let mut plan = crate::runs::models::CommandPlan { + verb: command.first().cloned().unwrap_or_default(), + selection: Default::default(), + settings: Default::default(), + }; + let mut tokens = command.iter().skip(1); + while let Some(token) = tokens.next() { + match token.as_str() { + "--full-refresh" => { + plan.settings + .insert("full_refresh".into(), serde_json::json!(true)); + } + "--warehouse" => { + let name = tokens.next().ok_or("--warehouse needs a name")?; + plan.settings + .insert("warehouse".into(), serde_json::json!(name)); + } + key if resources.iter().any(|resource| resource.key == key) => { + plan.selection.insert(key.to_owned()); + } + other => return Err(format!("{other} is not available for ad-hoc runs")), + } + } + Ok(plan) + } +} diff --git a/crates/sqlbuild-orchestration/tests/scheduler/ticks/helpers.rs b/crates/sqlbuild-orchestration/tests/scheduler/ticks/helpers.rs index 9ae555d007..0aba4ffc27 100644 --- a/crates/sqlbuild-orchestration/tests/scheduler/ticks/helpers.rs +++ b/crates/sqlbuild-orchestration/tests/scheduler/ticks/helpers.rs @@ -19,6 +19,7 @@ use crate::store::errors::ControlError; use crate::store::main::migrate_store::migrate_store; use crate::store::main::{begin::begin, commit::commit, query::query}; use crate::store::models::{ControlStore, Parameter, TransactionMode}; +use futures_util::{StreamExt, stream}; use serde_json::{Value, json}; use uuid::Uuid; @@ -36,6 +37,8 @@ use crate::store::tests::store_test_types::Backend; /// More than one due-trigger page of requesting triggers keeps every crash point reachable. const FAULT_TRIGGERS: usize = 400; const FAULT_BACKLOG_MINUTES: i64 = 1; +/// Recovery must bring every trigger to the latest due time within this many minutes. +const FAULT_CATCH_UP_MINUTES: i64 = 5; /// Calendar fixtures stay inside the tick-retention horizon: day 0 is midnight three days ago. pub(crate) fn calendar_time(day: i64) -> DateTime { @@ -618,21 +621,28 @@ pub(crate) async fn fault_run( .expect("recovery") .0; } + // Catch-up ticks only the latest missed time, so a trigger first evaluated after the next + // minute begins ticks that minute instead; each trigger must reach the latest due time. let settled = Utc::now(); - for _ in 0..FAULT_TRIGGERS.div_ceil(100) { - survivor = scheduler_cycle(&second, survivor) + let latest = settled + .duration_trunc(TimeDelta::minutes(1)) + .expect("minute"); + let deadline = settled + TimeDelta::minutes(FAULT_CATCH_UP_MINUTES); + let recovering = &second; + stream::unfold(survivor, |survivor| async move { + let survivor = scheduler_cycle(recovering, survivor) .await .expect("final recovery") .0; - } + Some((triggers_behind(recovering, latest).await, survivor)) + }) + .take_while(|behind| std::future::ready(*behind > 0 && Utc::now() < deadline)) + .for_each(|_| std::future::ready(())) + .await; let first = started .duration_trunc(TimeDelta::minutes(1)) .expect("minute") + TimeDelta::minutes(1); - let expected: BTreeSet> = (0..) - .map(|minutes| first + TimeDelta::minutes(minutes)) - .take_while(|time| *time <= settled) - .collect(); let mut ticks: Vec = Vec::new(); for trigger in &trigger_ids { let trigger_ticks = list_ticks(store, *trigger, 1000).await.expect("ticks"); @@ -641,15 +651,17 @@ pub(crate) async fn fault_run( .map(|tick| tick.scheduled_for) .collect(); anomalies.duplicate_ticks += trigger_ticks.len() - times.len(); - anomalies.missing_latest_ticks += usize::from( - expected - .last() - .is_some_and(|latest| !times.contains(latest)), - ); + anomalies.missing_latest_ticks += + usize::from(times.last().is_none_or(|time| *time < latest)); anomalies.off_schedule_ticks += times .iter() .filter(|time| **time < first || time.second() != 0 || time.nanosecond() != 0) .count(); + // A tick is never for a time after the clock reading it was planned and recorded with. + anomalies.off_schedule_ticks += trigger_ticks + .iter() + .filter(|tick| tick.scheduled_for > tick.evaluated_at) + .count(); ticks.extend(trigger_ticks); } let runs: BTreeSet = list_runs(store, (location, "development"), 5_000) @@ -683,13 +695,19 @@ pub(crate) async fn fault_run( let requested = usize::try_from(rows[0]["requested"].as_i64().expect("count")).expect("nonnegative count"); anomalies.request_event_mismatch = requested.abs_diff(requesting.len()); + // A second tick for a time the trigger already ticked must be refused. + let ticked = ticks + .iter() + .find(|tick| tick.trigger_id == trigger_id) + .expect("the first trigger ticked") + .scheduled_for; let transaction = begin(store, TransactionMode::Write) .await .expect("duplicate"); let duplicate = query(transaction, "INSERT INTO sqb_ticks (tick_id, trigger_id, scheduled_for, evaluated_at, epoch, outcome, run_id, covered_by_run_id) \ VALUES ($1,$2,$3,$3,1,'covered',NULL,NULL)", - vec![Parameter::Id(Uuid::now_v7()), Parameter::Id(trigger_id), Parameter::Time(*expected.last().expect("a due time"))]).await; + vec![Parameter::Id(Uuid::now_v7()), Parameter::Id(trigger_id), Parameter::Time(ticked)]).await; anomalies.accepted_duplicate_ticks = usize::from(duplicate.is_ok()); counts.insert("ticks", ticks.len()); close_store(second).await; @@ -697,6 +715,20 @@ pub(crate) async fn fault_run( Some((anomalies, counts, point)) } +/// Count triggers whose last tick precedes `latest`. +async fn triggers_behind(store: &ControlStore, latest: DateTime) -> i64 { + let transaction = begin(store, TransactionMode::Read).await.expect("progress"); + let (transaction, rows) = query( + transaction, + "SELECT COUNT(*) AS behind FROM sqb_trigger_state WHERE last_tick_for IS NULL OR last_tick_for<$1", + vec![Parameter::Time(latest)], + ) + .await + .expect("trigger progress"); + commit(transaction).await.expect("read progress"); + rows[0]["behind"].as_i64().expect("count") +} + /// Run one real scheduler loop until `deadline`, then stop it and return the trigger's ticks. pub(crate) async fn run_scheduler_until( store: &ControlStore, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/analysis/queries.rs b/crates/sqlbuild-python/src/bindings/_helpers/analysis/queries.rs index 98a6ce5ace..b719e29b6b 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/analysis/queries.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/analysis/queries.rs @@ -14,11 +14,24 @@ fn query_fingerprint(py: Python<'_>, sql: &str, dialect: &str) -> PyResult, spelling: &str, dialect: &str) -> PyResult> { + py.compiler_detach(|| { + sqlbuild_analysis::warehouse_types::main::parse_warehouse_type::parse_warehouse_type( + spelling, dialect, + ) + .map(|tree| tree.as_ref().map(serde_json::Value::to_string)) + }) + .map_err(value_error) +} + pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> { crate::bindings::_helpers::analysis::binding_catalog::register(module)?; crate::bindings::_helpers::analysis::required_columns::register(module)?; crate::bindings::_helpers::references::explicit_references::register(module)?; crate::bindings::_helpers::analysis::binding_locations::register(module)?; module.add_function(wrap_pyfunction!(query_fingerprint, module)?)?; + module.add_function(wrap_pyfunction!(parse_warehouse_type, module)?)?; Ok(()) } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs index da4b96cf18..4b9a2bd62a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs @@ -67,12 +67,6 @@ pub(crate) struct CommandScope<'py> { pub(crate) cli_vars: Bound<'py, PyAny>, } -/// A target's authored group warehouse with the `WarehouseGroup` it was found for. -pub(crate) struct FoundWarehouse<'py> { - pub(crate) authored: AuthoredWarehouse, - pub(crate) group: Bound<'py, PyAny>, -} - /// The project inputs and target a warehouse is resolved against. #[derive(Clone, Copy)] pub(crate) struct WarehouseInputs<'a, 'py> { @@ -122,11 +116,11 @@ fn resolved_warehouse<'py>( ) } -fn group_value(target: &Bound<'_, PyAny>, group: &Bound<'_, PyAny>) -> PyResult> { +fn group_value(target: &Bound<'_, PyAny>, group_name: &str) -> PyResult> { if target.is_none() { return Ok(None); } - let attribute: &str = if group.str()?.extract::()? == BUILD_GROUP { + let attribute: &str = if group_name == BUILD_GROUP { BUILD_GROUP } else { QUERY_GROUP @@ -138,11 +132,20 @@ fn group_value(target: &Bound<'_, PyAny>, group: &Bound<'_, PyAny>) -> PyResult< } /// The active target's authored, unexpanded warehouse for one command group. -pub(crate) fn find_authored<'py>( +pub(crate) fn find_authored( + inputs: WarehouseInputs<'_, '_>, + group: &Bound<'_, PyAny>, +) -> PyResult> { + let group_name: String = native_field(group, "value")?.extract()?; + authored_group(inputs, &group_name) +} + +/// The active target's authored, unexpanded warehouse for the group named `group_name`. +fn authored_group<'py>( inputs: WarehouseInputs<'_, 'py>, - group: &Bound<'py, PyAny>, -) -> PyResult>> { - let py: Python<'py> = group.py(); + group_name: &str, +) -> PyResult> { + let py: Python<'py> = inputs.discovered_inputs.py(); let project_config: Bound<'py, PyAny> = native_field(inputs.discovered_inputs, "project_config")?; let local_config: Bound<'py, PyAny> = native_field(inputs.discovered_inputs, "local_config")?; @@ -161,25 +164,20 @@ pub(crate) fn find_authored<'py>( let project_target: Bound<'py, PyAny> = native_field(&project_config, "targets")?.call_method1("get", (&target_name,))?; let target_name: String = target_name.extract()?; - let group_name: String = native_field(group, "value")?.extract()?; Ok(authored_warehouse( - (&target_name, &group_name), - group_value(&local_target, group)?, - group_value(&project_target, group)?, - ) - .map(|authored| FoundWarehouse { - authored, - group: group.clone(), - })) + (&target_name, group_name), + group_value(&local_target, group_name)?, + group_value(&project_target, group_name)?, + )) } /// The group warehouse with `${...}` templates expanded, checked as an identifier. fn expand_authored( - found: &FoundWarehouse<'_>, + authored: &AuthoredWarehouse, inputs: WarehouseInputs<'_, '_>, cli_vars: &Bound<'_, PyAny>, ) -> PyResult { - let py: Python<'_> = found.group.py(); + let py: Python<'_> = inputs.discovered_inputs.py(); let runtime: Bound<'_, PyAny> = build_effective_runtime_config( &inputs.discovered_inputs.clone(), Some(inputs.selected_target.clone()).filter(|value| !value.is_none()), @@ -187,7 +185,7 @@ fn expand_authored( ) .and_then(|value| pyo3::IntoPyObjectExt::into_bound_py_any(value, py))?; let expanded: Bound<'_, PyAny> = expanded_template_data( - PyString::new(py, &found.authored.warehouse).as_any(), + PyString::new(py, &authored.warehouse).as_any(), (runtime.get_item(1)?.downcast()?, &PyDict::new(py)), ( TemplateOptions { @@ -195,11 +193,11 @@ fn expand_authored( preserve_context_tokens: false, preserve_unknown_context: false, }, - &found.authored.context_label(), + &authored.context_label(), ), )?; let warehouse: String = expanded.str()?.extract()?; - check_expanded_warehouse(&found.authored, &warehouse).map_err(|error| raised(py, error))?; + check_expanded_warehouse(authored, &warehouse).map_err(|error| raised(py, error))?; Ok(warehouse) } @@ -257,7 +255,7 @@ pub(crate) fn resolve_warehouse( && let Some(found) = find_authored(inputs, group)? { let warehouse: String = expand_authored(&found, inputs, cli_vars)?; - return Ok((Some(warehouse), found.authored.source())); + return Ok((Some(warehouse), found.source())); } Ok(match connection_warehouse(connection_config)? { Some(warehouse) => (Some(warehouse), CONNECTION_WAREHOUSE_SOURCE.to_owned()), @@ -265,6 +263,22 @@ pub(crate) fn resolve_warehouse( }) } +/// Every warehouse the target configures: its `build` and `query` group warehouses and its connection warehouse. +pub(crate) fn configured_warehouses( + inputs: WarehouseInputs<'_, '_>, + connection_config: &Bound<'_, PyAny>, +) -> PyResult> { + let none: Bound<'_, PyAny> = none(connection_config.py()); + let mut names: std::collections::BTreeSet = std::collections::BTreeSet::new(); + for group_name in [BUILD_GROUP, QUERY_GROUP] { + if let Some(authored) = authored_group(inputs, group_name)? { + names.insert(expand_authored(&authored, inputs, &none)?); + } + } + names.extend(connection_warehouse(connection_config)?); + Ok(names) +} + fn optional_inputs<'a, 'py>( discovered_inputs: &'a Bound<'py, PyAny>, selected_target: &'a Bound<'py, PyAny>, @@ -397,7 +411,7 @@ pub(crate) fn describe_connection_warehouse<'py>( discovered_inputs: &discovered_inputs, selected_target: &selected_target, }; - let found: Option> = if scope.group.is_none() { + let found: Option = if scope.group.is_none() { None } else { find_authored(inputs, &scope.group)? @@ -409,7 +423,7 @@ pub(crate) fn describe_connection_warehouse<'py>( Err(error) => return Err(error), }; if expands_to { - return resolved_warehouse(py, Some(&warehouse), &found.authored.source()); + return resolved_warehouse(py, Some(&warehouse), &found.source()); } } resolved_warehouse(py, Some(&warehouse), CONNECTION_WAREHOUSE_SOURCE) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs index eaabced465..e7fd6c7603 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/analysis.rs @@ -6,6 +6,7 @@ use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyRes use pyo3::types::{PyBool, PyDict, PyDictMethods, PyString, PyTuple, PyTupleMethods}; use pyo3::{IntoPyObject, pyfunction, wrap_pyfunction}; use sqlbuild_host::entries::models::ExecutorHost; +use sqlbuild_rules::lint_text::main::counts_toward_exit::counts_toward_exit; use crate::bindings::_helpers::boundary::python_calls::{ Status, call_with, imported, keywords, within, @@ -546,7 +547,7 @@ impl<'py> Driver<'_, 'py> { } } -/// The core diagnostics followed by one rule-phase error per rule finding. +/// The core diagnostics followed by one rule-phase error per rule finding; lint-unavailable ones only warn. fn with_rule_diagnostics<'py>( py: Python<'py>, core: &Bound<'py, PyTuple>, @@ -554,6 +555,7 @@ fn with_rule_diagnostics<'py>( ) -> PyResult> { let phase: Bound<'py, PyAny> = enum_member(py, RecordId::DiagnosticPhase, "RULE")?; let severity: Bound<'py, PyAny> = enum_member(py, RecordId::DiagnosticSeverity, "ERROR")?; + let unavailable: Bound<'py, PyAny> = enum_member(py, RecordId::DiagnosticSeverity, "WARNING")?; let fix_note: Bound<'py, PyAny> = PyString::new(py, RULE_FIX_AVAILABLE_NOTE).into_any(); let mut diagnostics: Vec> = core.iter().collect(); for fault in native_field(rules_result, "findings")?.try_iter()? { @@ -568,7 +570,13 @@ fn with_rule_diagnostics<'py>( RecordId::CompilerDiagnostic, &[ ("phase", phase.clone()), - ("severity", severity.clone()), + ( + "severity", + match counts_toward_exit(&native_field(&fault, "code")?.extract::()?) { + true => severity.clone(), + false => unavailable.clone(), + }, + ), ("code", native_field(&fault, "code")?), ("message", native_field(&fault, "message")?), ("path", native_field(&fault, "path")?), diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_pipeline.rs b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_pipeline.rs index 59da81f65f..f8340fe4d9 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_pipeline.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compile_driver/plan_pipeline.rs @@ -1,9 +1,10 @@ //! The compile-and-plan pipeline of execution commands: compile, rules gate, then planning. use pyo3::prelude::{Bound, PyAny, PyAnyMethods, PyModule, PyModuleMethods, PyResult, Python}; -use pyo3::types::PyDict; +use pyo3::types::{PyDict, PyTuple}; use pyo3::{IntoPyObject, PyErr, pyfunction, wrap_pyfunction}; use sqlbuild_host::entries::models::ExecutorHost; +use sqlbuild_rules::lint_text::main::counts_toward_exit::counts_toward_exit; use sqlbuild_rules::rule_results::main::default_rules_notice::default_rules_notice; use crate::bindings::_helpers::boundary::python_calls::{KeywordArguments, keywords}; @@ -135,10 +136,17 @@ fn run_rules_gate<'py>( let message: String = rules_progress(elapsed, &result, true)?; on_progress.call1((message,))?; } - let findings: Bound<'py, PyAny> = native_field(&result, "findings")?; - if !findings.is_truthy()? { + let mut failing: Vec> = Vec::new(); + for finding in native_field(&result, "findings")?.try_iter()? { + let finding: Bound<'py, PyAny> = finding?; + if counts_toward_exit(&native_field(&finding, "code")?.extract::()?) { + failing.push(finding); + } + } + if failing.is_empty() { return Ok(()); } + let findings: Bound<'py, PyAny> = PyTuple::new(py, failing)?.into_any(); let summary: Bound<'py, PyAny> = new_record_with( py, RecordId::RulesResult, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/snapshot_execution.rs b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/snapshot_execution.rs index 93451ffa40..edd96b77f1 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/snapshot_execution.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/compiled_project/snapshot_execution.rs @@ -20,6 +20,8 @@ pub(crate) struct WorkerBuild { execution: RunExecution, completed: Mutex>, checks: Mutex>>, + /// Why the worker refused the run before executing anything, reported as the run's status reason. + refusal: Mutex>, } impl WorkerBuild { @@ -29,6 +31,7 @@ impl WorkerBuild { worker: worker.clone().unbind(), completed: Mutex::default(), checks: Mutex::default(), + refusal: Mutex::default(), }); for key in state.execution.skipped_nodes.keys() { state.complete(worker.py(), key, (NodeOutcome::Skipped, None, None))?; @@ -45,6 +48,12 @@ impl WorkerBuild { .any(|key| !self.execution.skipped_nodes.contains_key(key)) } + /// Record why the run is refused; the terminal report carries it as the run's status reason. + pub(crate) fn refuse(&self, reason: &str) -> PyResult<()> { + *self.refusal.lock().map_err(|_| poisoned())? = Some(reason.to_owned()); + Ok(()) + } + pub(crate) fn check_cancelled(&self, py: Python<'_>) -> PyResult<()> { if self.worker.bind(py).get().cancel_requested(py) { return Err(PyKeyboardInterrupt::new_err("run cancellation requested")); @@ -332,12 +341,18 @@ impl WorkerBuild { }, )?; } + let reason: String = self + .refusal + .lock() + .map_err(|_| poisoned())? + .clone() + .unwrap_or_else(|| "worker executor completed and closed its connections".into()); self.worker.bind(py).get().change( py, "worker-finished", RunChange::Finished { outcome, - reason: "worker executor completed and closed its connections".into(), + reason, exit_code: Some(i64::from(exit_code)), }, )?; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/contracts/model_contracts.rs b/crates/sqlbuild-python/src/bindings/_helpers/contracts/model_contracts.rs index b7ef9cdd1b..99d744dae5 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/contracts/model_contracts.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/contracts/model_contracts.rs @@ -51,7 +51,7 @@ pub(crate) fn evaluate_project_model_contracts( .compiler_detach(|| Ok(evaluate_model_contracts(&request))) .map_err(compiler_error)?; let outcomes: Vec> = - outcomes.map_err(|error| normalization_error(py, &error))?; + outcomes.map_err(|error| normalization_error(&error))?; Ok(models .iter() .zip(outcomes) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs b/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs index 0d8524bc71..456358a009 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/debug_command/run_debug.rs @@ -136,7 +136,7 @@ fn warehouse_checks( checks.push(WarehouseCheck::NotUsed { group: group_name, adapter_name: adapter_name.clone(), - source: found.authored.source(), + source: found.source(), }); } continue; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/location_command/worker.rs b/crates/sqlbuild-python/src/bindings/_helpers/location_command/worker.rs index 69ff6122f8..b8000d071a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/location_command/worker.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/location_command/worker.rs @@ -10,12 +10,16 @@ use sqlbuild_cli::argv::main::job_settings_arguments::job_settings_arguments; use sqlbuild_cli::dispatch::main::command_dispatch::command_dispatch; use sqlbuild_cli::dispatch::models::Dispatch; use sqlbuild_host::entries::models::ExecutorHost; -use sqlbuild_orchestration::runs::models::{RunOutcome, RunWindow}; +use sqlbuild_orchestration::runs::models::{RunKind, RunOutcome, RunRequest, RunWindow}; use crate::bindings::_helpers::boundary::python_calls::{KeywordArguments, call_with, within}; use crate::bindings::_helpers::build_command::command::run_build; use crate::bindings::_helpers::cli::argv::parse_cli_invocation; use crate::bindings::_helpers::cli::dispatch::{Invocation, command_facts, warehouse_scope}; +use crate::bindings::_helpers::command_runtime::warehouses::{ + WarehouseInputs, configured_warehouses, +}; +use crate::bindings::_helpers::compile_attachment::effective_config::build_effective_connection_config; use crate::bindings::_helpers::compiled_project::snapshot::NativeOrchestrationSnapshot; use crate::bindings::_helpers::compiled_project::snapshot_execution::WorkerBuild; use crate::bindings::_helpers::compiled_project::snapshot_worker::NativeOrchestrationWorker; @@ -89,6 +93,37 @@ fn build_request<'py>( )) } +/// Why an ad-hoc run's `--warehouse` is refused; published jobs keep their authored warehouse. +fn unconfigured_warehouse( + discovered: &Bound<'_, PyAny>, + frozen: &RunRequest, +) -> PyResult> { + if frozen.kind != RunKind::Adhoc { + return Ok(None); + } + let Some(warehouse) = frozen.settings.get("warehouse") else { + return Ok(None); + }; + let warehouse: &str = warehouse + .as_str() + .ok_or_else(|| PyValueError::new_err("frozen run warehouse must be text"))?; + let selected_target: Bound<'_, PyAny> = + PyString::new(discovered.py(), &frozen.target).into_any(); + let connection: Bound<'_, PyAny> = + build_effective_connection_config(discovered, Some(selected_target.clone()), None, false)?; + let inputs = WarehouseInputs { + discovered_inputs: discovered, + selected_target: &selected_target, + }; + if configured_warehouses(inputs, &connection)?.contains(warehouse) { + return Ok(None); + } + Ok(Some( + "the requested warehouse is not configured for this target on this location; \ + no warehouse statement ran", + )) +} + /// The heartbeat is native; only driver cancellation crosses the executor host boundary. fn run_cancellable( py: Python<'_>, @@ -153,6 +188,12 @@ pub(super) fn execute_worker<'py>( )?; return Ok(0); } + let frozen = owner.get().execution(py)?.claim.request; + if let Some(reason) = unconfigured_warehouse(&discovered, &frozen)? { + worker.refuse(reason)?; + write_stderr(py, &format!("Run refused: {reason}\n"))?; + return Err(PyValueError::new_err(reason)); + } let (request, scope) = build_request(py, discovered, arguments, &owner)?; write_stderr(py, "Run inputs verified. Executing frozen selection...\n")?; within(&scope, |_| run_cancellable(py, &worker, &request)) diff --git a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs index 19fc6051ec..36081f97c2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/plan_command/rules.rs @@ -5,6 +5,7 @@ use pyo3::prelude::{ }; use pyo3::types::{PyDict, PyDictMethods, PyTuple}; use pyo3::wrap_pyfunction; +use sqlbuild_rules::lint_text::main::counts_toward_exit::counts_toward_exit; use sqlbuild_rules::rule_catalogue::main::render_rule_list::render_rule_list; use sqlbuild_rules::rule_catalogue::models::RuleListing; @@ -246,7 +247,12 @@ fn run_rule_selection( native_field(request, "json_output")?.is_truthy()?, )?; print_line(py, &text, false)?; - let findings: usize = native_field(&result, "findings")?.len()?; + let mut findings: usize = 0; + for finding in native_field(&result, "findings")?.try_iter()? { + if counts_toward_exit(&native_field(&finding?, "code")?.extract::()?) { + findings += 1; + } + } if findings > 0 { print_line( py, diff --git a/crates/sqlbuild-python/src/bindings/_helpers/recall/command.rs b/crates/sqlbuild-python/src/bindings/_helpers/recall/command.rs index 5ba2193fc8..ed4516e2cb 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/recall/command.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/recall/command.rs @@ -33,6 +33,7 @@ use crate::bindings::_helpers::recall::constants::{ DEFAULT_MAX_CELL_BYTES, DEFAULT_QUERY_LIMIT, JSON_FORMAT, LAST_INVOCATION, RECALL_AMBIGUOUS_CODE, RECALL_DISABLED_CODE, RECALL_NOT_FOUND_CODE, RECALL_OPTIONS, RECALL_QUERY_CODE, RECALL_SETTINGS_CODE, RECALL_STORE_CODE, WAREHOUSE_BACKEND, + WAREHOUSE_STATE_RELATIONS_SQL, }; use crate::bindings::_helpers::recall::rendering::{QueryContext, render_result, render_schema}; use crate::bindings::_helpers::records::construct::new_error; @@ -402,10 +403,7 @@ fn run_schema(py: Python<'_>, recall: &RecallRequest) -> PyResult { ORDER BY table_schema, ordinal_position", relation.replace('\'', "''") ), - None => "SELECT table_schema, table_name, table_type FROM information_schema.tables \ - WHERE table_name LIKE '\\_sqlbuild\\_%' ESCAPE '\\' \ - ORDER BY table_schema, table_name" - .to_owned(), + None => WAREHOUSE_STATE_RELATIONS_SQL.to_owned(), }; return warehouse_query(py, recall, &project_dir, (&sql, None)); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/recall/constants.rs b/crates/sqlbuild-python/src/bindings/_helpers/recall/constants.rs index 2a1af6996c..2a10b7918f 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/recall/constants.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/recall/constants.rs @@ -26,6 +26,10 @@ pub(crate) const RECALL_AMBIGUOUS_CODE: &str = "C973"; pub(crate) const RECALL_QUERY_CODE: &str = "C974"; pub(crate) const RECALL_SETTINGS_CODE: &str = "C975"; pub(crate) const WAREHOUSE_BACKEND: &str = "warehouse"; +/// SQLBuild's state relations in the warehouse; a prefix comparison needs no `LIKE` escape, whose syntax differs by dialect. +pub(crate) const WAREHOUSE_STATE_RELATIONS_SQL: &str = "SELECT table_schema, table_name, table_type \ + FROM information_schema.tables WHERE SUBSTRING(table_name, 1, 10) = '_sqlbuild_' \ + ORDER BY table_schema, table_name"; /// Default cell width in text formats, in bytes. pub(crate) const DEFAULT_MAX_CELL_BYTES: usize = 4096; pub(crate) const JSON_FORMAT: &str = "json"; diff --git a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs index e211ebcefa..a404881bf2 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/rules/sql_rules.rs @@ -13,6 +13,7 @@ use pyo3::prelude::{ use pyo3::types::{PyBytes, PyDict, PyDictMethods, PyString, PyTuple}; use pyo3::{IntoPyObjectExt, pyfunction, wrap_pyfunction}; use sqlbuild_core::text::main::python_splitlines::python_splitlines; +use sqlbuild_rules::lint_text::main::counts_toward_exit::counts_toward_exit; use sqlbuild_rules::rule_cache::main::file_rule_key::file_rule_key; use sqlbuild_rules::rule_results::main::duplicate_literal_hints::duplicate_literal_hints; use sqlbuild_rules::rule_results::models::RuleFinding; @@ -670,7 +671,7 @@ fn lint_expansion_context( }) } -/// The findings of the selected SQL rules' violations; an engine failure leaves each unevaluated. +/// The selected SQL rules' findings and lint-unavailable warnings; an engine failure leaves each rule unevaluated. fn violation_findings( run: &SqlRun<'_, '_>, violations: &[(PathBuf, LintViolationRow)], @@ -686,6 +687,8 @@ fn violation_findings( let code: &str = &violation.code; if code == SQL_RULE_SUPPRESSION_CODE || run.codes.iter().any(|known| known == code) { evaluated.push(lint_finding((path, violation), &resolved_dir, model_paths)); + } else if !counts_toward_exit(code) { + evaluated.push(lint_finding((path, violation), &resolved_dir, &no_paths)); } else if code == engine_error { let finding: RuleFinding = lint_finding((path, violation), &resolved_dir, &no_paths); for rule_code in run.codes { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs index fc4d785cc2..8fdb86fd0a 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/completion.rs @@ -66,7 +66,7 @@ pub(crate) fn completed_diagnostics<'py>( let (completed, model_bindings, order) = py .compiler_detach(|| Ok(complete_semantic_diagnostics(&request, shared.catalog))) .map_err(compiler_error)? - .map_err(|failure| semantic_error(py, &failure))? + .map_err(|failure| semantic_error(&failure))? .into_parts(); let mut rebuilt: Vec> = Vec::with_capacity(completed.len()); for row in completed { diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/failures.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/failures.rs index 8535b64c6c..486267e427 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/failures.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/failures.rs @@ -1,7 +1,7 @@ //! The exception a failed native semantic completion step raises. +use pyo3::PyErr; use pyo3::exceptions::{PyTypeError, PyValueError}; -use pyo3::{PyErr, Python}; use sqlbuild_analysis::semantic_checks::models::SemanticFailure; use sqlbuild_core::constants::PANIC_MESSAGE; use sqlbuild_core::panics::main::is_compiler_panic::is_compiler_panic; @@ -15,13 +15,13 @@ const SEMANTIC_CONTEXT: &str = "native semantic completion"; const NO_DIALECT_MESSAGE: &str = "argument 'dialect': 'None' is not an instance of 'str'"; /// The error Python's semantic completion raised for the same input, or `NativeCompilerError`. -pub(crate) fn semantic_error(python: Python<'_>, failure: &SemanticFailure) -> PyErr { +pub(crate) fn semantic_error(failure: &SemanticFailure) -> PyErr { match failure { SemanticFailure::NoDialect => PyTypeError::new_err(NO_DIALECT_MESSAGE), SemanticFailure::UnknownDialect(name) => { PyValueError::new_err(format!("Unknown dialect: {name}")) } - SemanticFailure::TypeNormalization(error) => normalization_error(python, error), + SemanticFailure::TypeNormalization(error) => normalization_error(error), SemanticFailure::Internal(reason) => internal_error(reason), } } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs index 422162374f..629a05e747 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/metadata.rs @@ -70,7 +70,7 @@ pub(crate) fn metadata_diagnostics<'py>( let outcome = py .compiler_detach(|| Ok(check_semantic_metadata(&request, catalog))) .map_err(compiler_error)? - .map_err(|failure| semantic_error(py, &failure))?; + .map_err(|failure| semantic_error(&failure))?; let dialect: Bound<'py, PyAny> = native_field(profile, "sql_analysis_dialect")?; for type_sql in &outcome.fallback_types { let _ = classes.call( diff --git a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs index c75a2b2af4..eddbc4fc81 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/semantic_checks/type_recovery.rs @@ -93,7 +93,7 @@ pub(crate) fn recovered_types<'py>( let step = py .compiler_detach(|| Ok(plan_type_recovery(&request, shared.catalog))) .map_err(compiler_error)? - .map_err(|failure| semantic_error(py, &failure))?; + .map_err(|failure| semantic_error(&failure))?; if step.status() == UNCHANGED { return Ok(project.clone()); } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs b/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs index 125c688b3c..805d5de931 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs @@ -16,7 +16,7 @@ use crate::bindings::_helpers::native_host::host; use crate::bindings::_helpers::type_system::normalization::normalization_error; const TYPE_NORMALIZATION_LOGGER: &str = "sqlbuild.adapter"; -const TYPE_PARSE_FALLBACK_MESSAGE: &str = "type normalization polyglot parse failed; falling back"; +const TYPE_PARSE_FALLBACK_MESSAGE: &str = "type normalization parse failed; falling back"; const DEFAULT_DIALECT: &str = "generic"; const CACHE_LIMIT: usize = 1024; @@ -37,7 +37,6 @@ pub(crate) fn types_equal(dialect: &Bound<'_, PyAny>, left: &str, right: &str) - } fn normalized(dialect: &Bound<'_, PyAny>, name: &str, type_sql: &str) -> PyResult { - let py: Python<'_> = dialect.py(); let cache: &Cache = NORMALIZED.get_or_init(|| Mutex::new(HashMap::new())); let key: (String, String) = (type_sql.to_owned(), name.to_owned()); let cached: Option = cache @@ -52,7 +51,7 @@ fn normalized(dialect: &Bound<'_, PyAny>, name: &str, type_sql: &str) -> PyResul normalized, parse_error, } = compiler_guard(|| Ok(normalize_type(type_sql, name)))? - .map_err(|error| normalization_error(py, &error))?; + .map_err(|error| normalization_error(&error))?; if let Some(parse_error) = parse_error { log_parse_fallback(dialect, type_sql, &parse_error)?; } diff --git a/crates/sqlbuild-python/src/bindings/_helpers/type_system/normalization.rs b/crates/sqlbuild-python/src/bindings/_helpers/type_system/normalization.rs index 74fe591603..3193722270 100644 --- a/crates/sqlbuild-python/src/bindings/_helpers/type_system/normalization.rs +++ b/crates/sqlbuild-python/src/bindings/_helpers/type_system/normalization.rs @@ -1,14 +1,13 @@ //! Native type normalization. use pyo3::exceptions::PyValueError; -use pyo3::prelude::{Bound, PyAnyMethods, PyErr, PyModule, PyModuleMethods, PyResult, Python}; +use pyo3::prelude::{Bound, PyErr, PyModule, PyModuleMethods, PyResult}; use pyo3::{pyfunction, wrap_pyfunction}; use sqlbuild_analysis::type_system::errors::TypeNormalizationError; use sqlbuild_analysis::type_system::main::normalize_type::normalize_type; use sqlbuild_analysis::type_system::models::{NormalizedType, TypeNormalization}; use crate::bindings::_helpers::boundary::panics::compiler_guard; -use crate::bindings::_helpers::records::fields::field as native_field; positional_record! { into; @@ -22,11 +21,10 @@ positional_record! { } } -/// Normalize one type: `(normalized row, Polyglot parse error)`, or Python's exception. +/// Normalize one type: `(normalized row, parse error)`, or Python's exception. #[pyfunction] #[pyo3(name = "normalize_type")] fn normalize_type_binding( - python: Python<'_>, type_sql: &str, dialect: &str, ) -> PyResult<(NormalizedRow, Option)> { @@ -37,20 +35,13 @@ fn normalize_type_binding( normalized, parse_error, }) => Ok((normalized_row(normalized), parse_error)), - Err(error) => Err(normalization_error(python, &error)), + Err(error) => Err(normalization_error(&error)), } } -/// The exception the Polyglot wheel raises for the same failure. -pub(crate) fn normalization_error(python: Python<'_>, error: &TypeNormalizationError) -> PyErr { - match error { - TypeNormalizationError::UnknownDialect(_) => PyValueError::new_err(error.message()), - TypeNormalizationError::Generation(_) => python - .import("polyglot_sql") - .and_then(|module| native_field(&module, "PolyglotError")) - .and_then(|class| class.call1((error.message(),))) - .map_or_else(|import_error| import_error, PyErr::from_value), - } +/// Python's exception for a type that cannot be normalized. +pub(crate) fn normalization_error(error: &TypeNormalizationError) -> PyErr { + PyValueError::new_err(error.message()) } fn normalized_row(normalized: NormalizedType) -> NormalizedRow { diff --git a/crates/sqlbuild-rules/Cargo.toml b/crates/sqlbuild-rules/Cargo.toml index 535ee3687a..1070397264 100644 --- a/crates/sqlbuild-rules/Cargo.toml +++ b/crates/sqlbuild-rules/Cargo.toml @@ -11,7 +11,7 @@ rust-version.workspace = true blake3.workspace = true fensu-policy.workspace = true globset.workspace = true -polyglot-sql.workspace = true +sqlquack-syntax.workspace = true rayon.workspace = true regex.workspace = true rustc-hash.workspace = true diff --git a/crates/sqlbuild-rules/src/constants.rs b/crates/sqlbuild-rules/src/constants.rs index 37b4e3f953..e3f4b5ad79 100644 --- a/crates/sqlbuild-rules/src/constants.rs +++ b/crates/sqlbuild-rules/src/constants.rs @@ -55,3 +55,6 @@ pub(crate) const ALLOWED_QUALIFIERS_OPTION: &str = "allowed_qualifiers"; pub(crate) const ALLOWED_PATHS_KEY: &str = "paths"; /// The published layers when a project does not configure them. pub(crate) const DEFAULT_PUBLISHED_LAYERS: [&str; 2] = ["mart", "mart_v"]; +/// What to do when SQLBuild does not parse a project's dialect for built-in rules. +pub(crate) const RULES_UNAVAILABLE_REMEDIATION: &str = + "These rules did not run. Review this file by hand until built-in rules support this dialect."; diff --git a/crates/sqlbuild-rules/src/engine/_helpers/evaluation.rs b/crates/sqlbuild-rules/src/engine/_helpers/evaluation.rs index 64d11d4ead..d76c61a0cb 100644 --- a/crates/sqlbuild-rules/src/engine/_helpers/evaluation.rs +++ b/crates/sqlbuild-rules/src/engine/_helpers/evaluation.rs @@ -1,6 +1,7 @@ use crate::configuration::main::validate as config; use crate::constants::{API_VERSION, NATIVE_BUILD_IDENTITY, RELATION_EXCEPTION_RULES}; use crate::engine::_helpers::cache::{Cache, PendingBucketWrite, RuleCacheEntry}; +use crate::lint_text::constants::LINT_UNAVAILABLE_CODE; use crate::models::{ EvaluateRequest, EvaluateResponse, Fault, Model, RuleException, RuleMetadata, RuleScope, RulesCodeGrammar, RulesConfig, @@ -580,11 +581,15 @@ pub(crate) fn finalize_findings( apply_fault_policy(&request, evaluated_codes, findings) } +/// Apply rule exceptions to `faults`; lint-unavailable warnings report our gap, not a rule, so they pass through unsuppressed. fn apply_fault_policy( request: &EvaluateRequest, evaluated_codes: &[String], faults: Vec, ) -> Result, String> { + let (unavailable, faults): (Vec, Vec) = faults + .into_iter() + .partition(|fault| fault.code == LINT_UNAVAILABLE_CODE); let unevaluated: BTreeSet<_> = faults .iter() .filter(|fault| fault.unevaluated) @@ -641,6 +646,7 @@ fn apply_fault_policy( )); fault }) + .chain(unavailable) .collect()) } diff --git a/crates/sqlbuild-rules/src/engine/tests/test_empty_input_tests.rs b/crates/sqlbuild-rules/src/engine/tests/test_empty_input_tests.rs index 3c9f34d22b..92657d96c2 100644 --- a/crates/sqlbuild-rules/src/engine/tests/test_empty_input_tests.rs +++ b/crates/sqlbuild-rules/src/engine/tests/test_empty_input_tests.rs @@ -14,6 +14,7 @@ const NO_ROWS_ASSERTION: (&str, &str) = ( "__assert__empty_inputs_produce_no_rows", "SELECT 1 AS unexpected_row FROM __ref(\"orders\")", ); +const UNAVAILABLE_MESSAGE: &str = "L007 tests/unit/test_orders__empty_inputs_produce_no_rows.sql: SQLBuild cannot parse mssql SQL for built-in rules yet, so these rules were not evaluated: SQBRTEST203"; const FLAGGED_MESSAGE: &str = "SQBRTEST203 tests/unit/test_orders__empty_inputs_produce_no_rows.sql: unit test block 1 (\"orders__empty_inputs_produce_no_rows\") mocks only empty inputs and asserts only that no rows are produced"; #[test] @@ -419,7 +420,7 @@ fn given_cached_minimum_tests_when_filler_becomes_real_test_then_model_cache_is_ } #[test] -fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_type_arguments_are_not_calls() +fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_reports_the_dialect_unavailable() -> Result<(), String> { let project_dir = TempDir::new().map_err(|error| error.to_string())?; let empty_orders = ( @@ -428,7 +429,7 @@ fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_type_argum ); let test_cases = [ test_types::EmptyInputTestRuleTestCase { - description: "a CONVERT to a parameterized type under a false predicate is an empty mock", + description: "a CONVERT to a parameterized type under a false predicate is an empty mock, reported unavailable until SQLBuild parses the dialect", test: helpers::empty_input_test_fact( "orders__empty_inputs_produce_no_rows", &[( @@ -439,10 +440,10 @@ fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_type_argum &[NO_ROWS_ASSERTION], ), allowed_tests: json!([]), - expected_messages: &[FLAGGED_MESSAGE], + expected_messages: &[UNAVAILABLE_MESSAGE], }, test_types::EmptyInputTestRuleTestCase { - description: "a CONVERT with a style argument is an empty mock", + description: "a CONVERT with a style argument is an empty mock, reported unavailable until SQLBuild parses the dialect", test: helpers::empty_input_test_fact( "orders__empty_inputs_produce_no_rows", &[( @@ -453,10 +454,10 @@ fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_type_argum &[NO_ROWS_ASSERTION], ), allowed_tests: json!([]), - expected_messages: &[FLAGGED_MESSAGE], + expected_messages: &[UNAVAILABLE_MESSAGE], }, test_types::EmptyInputTestRuleTestCase { - description: "a TRY_CONVERT to a parameterized type keeps a bare existence check", + description: "a TRY_CONVERT to a parameterized type keeps a bare existence check, reported unavailable until SQLBuild parses the dialect", test: helpers::empty_input_test_fact( "orders__empty_inputs_produce_no_rows", &[empty_orders], @@ -467,10 +468,10 @@ fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_type_argum )], ), allowed_tests: json!([]), - expected_messages: &[FLAGGED_MESSAGE], + expected_messages: &[UNAVAILABLE_MESSAGE], }, test_types::EmptyInputTestRuleTestCase { - description: "a function call in a CONVERT value argument still counts", + description: "a function call in a CONVERT value argument still counts, reported unavailable until SQLBuild parses the dialect", test: helpers::empty_input_test_fact( "orders__empty_inputs_produce_no_rows", &[( @@ -481,7 +482,7 @@ fn given_sql_server_conversions_when_evaluating_empty_input_rule_then_type_argum &[NO_ROWS_ASSERTION], ), allowed_tests: json!([]), - expected_messages: &[], + expected_messages: &[UNAVAILABLE_MESSAGE], }, ]; diff --git a/crates/sqlbuild-rules/src/engine/tests/test_evaluation.rs b/crates/sqlbuild-rules/src/engine/tests/test_evaluation.rs index c46bd6e88e..4557377612 100644 --- a/crates/sqlbuild-rules/src/engine/tests/test_evaluation.rs +++ b/crates/sqlbuild-rules/src/engine/tests/test_evaluation.rs @@ -29,11 +29,13 @@ fn given_dependency_tokens_when_classifying_imports_then_preserves_dialect_and_o expected_messages: &[], }, test_types::DependencyTokenTestCase { - description: "backtick quoting and comment-separated source arguments", + description: "backtick quoting and comment-separated source arguments, reported unavailable until SQLBuild parses the dialect", expected_columns: &[], dialect: "bigquery", query_sql: "WITH imported AS (SELECT * FROM __source /* ignored */ (`Orders`)) SELECT order_id FROM imported", - expected_messages: &[], + expected_messages: &[ + "SQLBuild cannot parse bigquery SQL for built-in rules yet, so these rules were not evaluated: SQBRMODEL101", + ], }, test_types::DependencyTokenTestCase { description: "dollar strings do not contribute spurious calls", @@ -508,19 +510,38 @@ fn given_scoped_and_global_declarations_when_evaluating_domain_rule_then_only_gl fn given_snowflake_expression_when_evaluating_rules_then_uses_project_dialect() -> Result<(), String> { let project_dir = TempDir::new().map_err(|error| error.to_string())?; - let test_cases = [test_types::DialectEvaluationTestCase { - description: "Snowflake compiler expressions use the resolved project dialect", - dialect: "snowflake", - query_sql: concat!( - "SELECT * EXCLUDE (ignored), ", - "TRANSFORM(values, leg INT -> leg + 1) AS adjusted, ", - "payload:product_id::STRING AS product_id, ", - "CAST(amounts AS ARRAY(NUMBER(38, 10))) AS amounts, ", - "OBJECT_CONSTRUCT_KEEP_NULL('group_id', group_id) AS details, ", - "FROM __table_fn(\"inventory_items\")(42)" - ), - expected_code: "SQBRCONTRACT101", - }]; + let test_cases = [ + test_types::DialectEvaluationTestCase { + description: "Snowflake compiler expressions use the resolved project dialect", + dialect: "snowflake", + query_sql: concat!( + "SELECT * EXCLUDE (ignored), ", + "TRANSFORM(values, leg INT -> leg + 1) AS adjusted, ", + "payload:product_id::STRING AS product_id, ", + "CAST(amounts AS ARRAY(NUMBER(38, 10))) AS amounts, ", + "OBJECT_CONSTRUCT_KEEP_NULL('group_id', group_id) AS details, ", + "FROM __table_fn(\"inventory_items\")(42)" + ) + .to_owned(), + expected_code: "SQBRCONTRACT101", + }, + test_types::DialectEvaluationTestCase { + description: "a Snowflake table function in an aliased CTE source parses as SQLBuild renders it", + dialect: "snowflake", + query_sql: concat!( + "WITH lines AS (SELECT * FROM __table_fn(\"lines_for\")('B-1', __ref(\"orders\").k) AS l)\n", + "SELECT l.item_id FROM lines AS l" + ) + .to_owned(), + expected_code: "SQBRCONTRACT101", + }, + test_types::DialectEvaluationTestCase { + description: "a Snowflake table function already written inside TABLE( ) is not wrapped twice", + dialect: "snowflake", + query_sql: "SELECT item_id FROM table ( __table_fn(\"inventory_items\")(42) )".to_owned(), + expected_code: "SQBRCONTRACT101", + }, + ]; for test_case in test_cases { let config = json!({"select": ["SQBRCONTRACT101"], "cache": {"enabled": false}}); @@ -535,6 +556,11 @@ fn given_snowflake_expression_when_evaluating_rules_then_uses_project_dialect() "{}", test_case.description ); + assert_ne!( + result["faults"][0]["unevaluated"], true, + "{}: {result}", + test_case.description + ); } Ok(()) } @@ -550,26 +576,29 @@ fn given_dialect_quoting_around_table_functions_when_evaluating_rules_then_model query_sql: concat!( "SELECT 'it\\'s (' AS label, item_id ", "FROM __table_fn(\"inventory_items\")(42)" - ), + ) + .to_owned(), expected_code: "SQBRCONTRACT101", }, test_types::DialectEvaluationTestCase { - description: "BigQuery backslash-escaped quote inside table function arguments", + description: "BigQuery backslash-escaped quote inside table function arguments, reported unavailable until SQLBuild parses the dialect", dialect: "bigquery", query_sql: concat!( "SELECT item_id ", "FROM __table_fn(\"inventory_items\", 'a\\')')(42)" - ), - expected_code: "SQBRCONTRACT101", + ) + .to_owned(), + expected_code: "L007", }, test_types::DialectEvaluationTestCase { - description: "BigQuery backtick identifier containing a parenthesis in arguments", + description: "BigQuery backtick identifier containing a parenthesis in arguments, reported unavailable until SQLBuild parses the dialect", dialect: "bigquery", query_sql: concat!( "SELECT item_id ", "FROM __table_fn(\"inventory_items\", `batch)size`)(42)" - ), - expected_code: "SQBRCONTRACT101", + ) + .to_owned(), + expected_code: "L007", }, ]; @@ -593,12 +622,27 @@ fn given_dialect_quoting_around_table_functions_when_evaluating_rules_then_model #[test] fn given_unparseable_model_when_evaluating_then_retains_named_rule_fault() -> Result<(), String> { let project_dir = TempDir::new().map_err(|error| error.to_string())?; - let test_cases = [test_types::DialectEvaluationTestCase { - description: "invalid query is missing evidence rather than a passing contract", - dialect: "duckdb", - query_sql: "SELECT (", - expected_code: "SQBRCONTRACT101", - }]; + let deep_sql = format!("SELECT {}1 AS order_id FROM orders", "1 + ".repeat(50_000)); + let test_cases = [ + test_types::DialectEvaluationTestCase { + description: "invalid query is missing evidence rather than a passing contract", + dialect: "duckdb", + query_sql: "SELECT (".to_owned(), + expected_code: "SQBRCONTRACT101", + }, + test_types::DialectEvaluationTestCase { + description: "a DuckDB expression deeper than the AST limit is refused, not a crash", + dialect: "duckdb", + query_sql: deep_sql.clone(), + expected_code: "SQBRCONTRACT101", + }, + test_types::DialectEvaluationTestCase { + description: "a Snowflake expression deeper than the AST limit is refused, not a crash", + dialect: "snowflake", + query_sql: deep_sql, + expected_code: "SQBRCONTRACT101", + }, + ]; for test_case in test_cases { let config = json!({"select": ["SQBRCONTRACT101"], "cache": {"enabled": false}}); let mut request: Value = serde_json::from_str(&helpers::request(&project_dir, &config)) @@ -641,19 +685,19 @@ fn given_comment_apostrophes_when_generic_fallback_normalizes_rules_sql_then_mod test_types::DialectEvaluationTestCase { description: "line comment apostrophe before a colon path", dialect: "postgres", - query_sql: "-- don't drop the customer path\nSELECT payload:customer_id AS customer_id FROM orders", + query_sql: "-- don't drop the customer path\nSELECT payload:customer_id AS customer_id FROM orders".to_owned(), expected_code: "SQBRCONTRACT101", }, test_types::DialectEvaluationTestCase { description: "block comment apostrophe before a colon path", dialect: "postgres", - query_sql: "/* customer's path */ SELECT payload:customer_id AS customer_id FROM orders", + query_sql: "/* customer's path */ SELECT payload:customer_id AS customer_id FROM orders".to_owned(), expected_code: "SQBRCONTRACT101", }, test_types::DialectEvaluationTestCase { description: "colon inside a line comment stays commented", dialect: "postgres", - query_sql: "SELECT payload:customer_id AS customer_id -- note: it's nested\nFROM orders", + query_sql: "SELECT payload:customer_id AS customer_id -- note: it's nested\nFROM orders".to_owned(), expected_code: "SQBRCONTRACT101", }, ]; diff --git a/crates/sqlbuild-rules/src/engine/tests/test_query_facts.rs b/crates/sqlbuild-rules/src/engine/tests/test_query_facts.rs index b03abed424..fca1058f78 100644 --- a/crates/sqlbuild-rules/src/engine/tests/test_query_facts.rs +++ b/crates/sqlbuild-rules/src/engine/tests/test_query_facts.rs @@ -32,13 +32,13 @@ fn given_dialect_sql_when_reading_query_facts_then_findings_match_the_authored_s expected_faults: &[], }, QueryFactsTestCase { - description: "an apostrophe in a BigQuery # comment opens no string", + description: "an apostrophe in a BigQuery # comment opens no string, reported unavailable until SQLBuild parses the dialect", dialect: "bigquery", query_sql: "WITH imported AS (\n # don't read the archive\n SELECT * FROM __ref(\"orders\")\n)\nSELECT order_id FROM imported", select: IMPORT_RULES, enum_columns: &[], columns: json!([]), - expected_faults: &[], + expected_faults: &[("L007", 1, 1, "cannot parse bigquery SQL for built-in rules")], }, QueryFactsTestCase { description: "a DuckDB FROM-first import CTE star is exempt without a SELECT keyword", @@ -115,6 +115,48 @@ fn given_dialect_sql_when_reading_query_facts_then_findings_match_the_authored_s columns: json!([]), expected_faults: &[("SQBRDECLARATION102", 1, 35, "3 < 7")], }, + QueryFactsTestCase { + description: "an anchorless comparison nested in another keeps its own position", + dialect: "snowflake", + query_sql: "SELECT order_id FROM orders\nWHERE CASE WHEN 3 = 2 THEN 10 ELSE 20 END = 1", + select: NUMERIC_RULE, + enum_columns: &[], + columns: json!([]), + expected_faults: &[("SQBRDECLARATION102", 2, 17, "3 = 2")], + }, + QueryFactsTestCase { + description: "a Snowflake LAST_DAY date part compared with an enum column is no enum literal", + dialect: "snowflake", + query_sql: "SELECT order_id FROM orders WHERE LAST_DAY(placed_at, 'month') = status", + select: ENUM_RULE, + enum_columns: STATUS, + columns: json!([]), + expected_faults: &[], + }, + QueryFactsTestCase { + description: "DuckDB literal comparisons on separate lines each keep their own position", + dialect: "duckdb", + query_sql: "SELECT order_id FROM orders\nWHERE 3 < 7\n AND 5 > 2", + select: NUMERIC_RULE, + enum_columns: &[], + columns: json!([]), + expected_faults: &[ + ("SQBRDECLARATION102", 2, 7, "3 < 7"), + ("SQBRDECLARATION102", 3, 7, "5 > 2"), + ], + }, + QueryFactsTestCase { + description: "Snowflake literal comparisons on separate lines each keep their own position", + dialect: "snowflake", + query_sql: "SELECT order_id FROM orders\nWHERE 3 < 7\n AND 5 > 2", + select: NUMERIC_RULE, + enum_columns: &[], + columns: json!([]), + expected_faults: &[ + ("SQBRDECLARATION102", 2, 7, "3 < 7"), + ("SQBRDECLARATION102", 3, 7, "5 > 2"), + ], + }, QueryFactsTestCase { description: "unaliased later union branches keep their cast types", dialect: "duckdb", @@ -181,22 +223,22 @@ fn given_dialect_sql_when_reading_query_facts_then_findings_match_the_authored_s )], }, QueryFactsTestCase { - description: "a BigQuery double-quoted string compared with an enum column", + description: "a BigQuery double-quoted string compared with an enum column, reported unavailable until SQLBuild parses the dialect", dialect: "bigquery", query_sql: "SELECT order_id FROM orders WHERE status = \"active\"", select: ENUM_RULE, enum_columns: STATUS, columns: json!([]), - expected_faults: &[("SQBRDECLARATION101", 1, 35, "")], + expected_faults: &[("L007", 1, 1, "cannot parse bigquery SQL for built-in rules")], }, QueryFactsTestCase { - description: "a BigQuery triple-quoted string compared with an enum column", + description: "a BigQuery triple-quoted string compared with an enum column, reported unavailable until SQLBuild parses the dialect", dialect: "bigquery", query_sql: "SELECT order_id FROM orders WHERE status = '''active'''", select: ENUM_RULE, enum_columns: STATUS, columns: json!([]), - expected_faults: &[("SQBRDECLARATION101", 1, 35, "")], + expected_faults: &[("L007", 1, 1, "cannot parse bigquery SQL for built-in rules")], }, QueryFactsTestCase { description: "a Postgres escape string compared with an enum column", @@ -226,32 +268,22 @@ fn given_dialect_sql_when_reading_query_facts_then_findings_match_the_authored_s expected_faults: &[("SQBRDECLARATION102", 1, 35, "amount == 42")], }, QueryFactsTestCase { - description: "a parenthesized T-SQL TOP argument is not projection", + description: "a parenthesized T-SQL TOP argument is not projection, reported unavailable until SQLBuild parses the dialect", dialect: "mssql", query_sql: "SELECT TOP (5) amount + 1 AS amount FROM orders", select: OUTPUT_TYPE_RULE, enum_columns: &[], columns: json!([{"name": "amount", "type": "INTEGER", "type_proven": true}]), - expected_faults: &[( - "SQBRCONTRACT105", - 1, - 16, - "calculated without an outer explicit cast", - )], + expected_faults: &[("L007", 1, 1, "cannot parse mssql SQL for built-in rules")], }, QueryFactsTestCase { - description: "a T-SQL TOP PERCENT clause is not projection", + description: "a T-SQL TOP PERCENT clause is not projection, reported unavailable until SQLBuild parses the dialect", dialect: "mssql", query_sql: "SELECT TOP 5 PERCENT amount + 1 AS amount FROM orders", select: OUTPUT_TYPE_RULE, enum_columns: &[], columns: json!([{"name": "amount", "type": "INTEGER", "type_proven": true}]), - expected_faults: &[( - "SQBRCONTRACT105", - 1, - 22, - "calculated without an outer explicit cast", - )], + expected_faults: &[("L007", 1, 1, "cannot parse mssql SQL for built-in rules")], }, QueryFactsTestCase { description: "a placeholder in relation position still parses", diff --git a/crates/sqlbuild-rules/src/engine/tests/test_types.rs b/crates/sqlbuild-rules/src/engine/tests/test_types.rs index 24d53f6a12..7bd4f88f21 100644 --- a/crates/sqlbuild-rules/src/engine/tests/test_types.rs +++ b/crates/sqlbuild-rules/src/engine/tests/test_types.rs @@ -35,7 +35,7 @@ pub(crate) struct DeclarationScopeTestCase { pub(crate) struct DialectEvaluationTestCase { pub(crate) description: &'static str, pub(crate) dialect: &'static str, - pub(crate) query_sql: &'static str, + pub(crate) query_sql: String, pub(crate) expected_code: &'static str, } diff --git a/crates/sqlbuild-rules/src/lint_text/_helpers/sql_diagnostics.rs b/crates/sqlbuild-rules/src/lint_text/_helpers/sql_diagnostics.rs index d7ddf43c3c..a2d51c33d3 100644 --- a/crates/sqlbuild-rules/src/lint_text/_helpers/sql_diagnostics.rs +++ b/crates/sqlbuild-rules/src/lint_text/_helpers/sql_diagnostics.rs @@ -1,6 +1,7 @@ //! Native SQL lint responses mapped back onto authored source through expansion passes. use std::borrow::Cow; +use std::collections::HashSet; use std::sync::LazyLock; use regex::Regex; @@ -10,7 +11,8 @@ use crate::lint_text::_helpers::sites::backtick_identifiers; use crate::lint_text::_helpers::sites::{byte_offset, compiled, lint_policy, site_end}; use crate::lint_text::constants::{ GENERATED_FIX, GENERATED_SQL_SUFFIX, HARNESS_CTE_PREFIXES, INVALID_DIAGNOSTIC, - INVALID_FIX_EDIT, INVALID_FIX_OBJECT, INVALID_FIX_REASON, LINT_API_VERSION, LONG_LITERAL_CODE, + INVALID_FIX_EDIT, INVALID_FIX_OBJECT, INVALID_FIX_REASON, INVALID_UNAVAILABLE, + LINT_API_VERSION, LINT_UNAVAILABLE_CODE, LINT_UNAVAILABLE_REMEDIATION, LONG_LITERAL_CODE, MACRO_MIXED_QUALIFICATION_REMEDIATION, MISSING_DIAGNOSTICS, MIXED_QUALIFICATION_CODE, NATIVE_LINT_ERROR_CODE, NON_CONTIGUOUS_FIX, NON_OBJECT_RESPONSE, NOT_AN_OBJECT, PARSE_ERROR_POSITION_PATTERN, PARSE_FAILURE_PREFIX, PARSE_FAILURE_REMEDIATION, @@ -109,8 +111,15 @@ pub(crate) fn map_offset(offset: i64, passes: &[Vec]) -> (i64, bo (current, generated) } -/// The diagnostics list of one engine response, or the error it reports. -pub(crate) fn response_diagnostics(response: &Value) -> Result, String> { +/// The diagnostics list of one engine response, or why its dialect could not be linted. +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum LintResult { + Diagnostics(Vec), + Unavailable(String), +} + +/// The result of one engine response, or the error it reports. +pub(crate) fn response_diagnostics(response: &Value) -> Result { let Some(payload) = response.as_object() else { return Err(NON_OBJECT_RESPONSE.to_string()); }; @@ -123,9 +132,67 @@ pub(crate) fn response_diagnostics(response: &Value) -> Result, Strin if integer(result.get("version")) != Some(LINT_API_VERSION) { return Err(UNSUPPORTED_VERSION.to_string()); } - match result.get("diagnostics") { - Some(Value::Array(diagnostics)) => Ok(diagnostics.clone()), - _ => Err(MISSING_DIAGNOSTICS.to_string()), + match (result.get("unavailable"), result.get("diagnostics")) { + (Some(Value::String(reason)), _) => Ok(LintResult::Unavailable(reason.clone())), + (Some(_), _) => Err(INVALID_UNAVAILABLE.to_string()), + (None, Some(Value::Array(diagnostics))) => Ok(LintResult::Diagnostics(diagnostics.clone())), + (None, _) => Err(MISSING_DIAGNOSTICS.to_string()), + } +} + +/// One body's authored diagnostics from its engine response, or the warning that it was not linted. +pub(crate) fn body_diagnostics( + response: &Result, + (body, omitted): (&LintSqlBody, Option), + file: &AuthoredFile<'_>, +) -> Vec { + let raw: Vec = match response { + Ok(LintResult::Diagnostics(raw)) => raw.clone(), + Ok(LintResult::Unavailable(reason)) => { + return vec![unavailable_warning(reason, body, file)]; + } + Err(message) => return vec![parse_failure(message, body, file)], + }; + without_generated_frame(raw, body, omitted) + .iter() + .filter_map(|item| match authored_diagnostic(item, body, file) { + Ok(diagnostic) => diagnostic, + Err(message) => Some(parse_failure(&message, body, file)), + }) + .collect() +} + +/// The diagnostics with only each file's first lint-unavailable warning. +pub(crate) fn unavailable_once_per_file( + diagnostics: Vec, +) -> Vec { + let mut warned: HashSet = HashSet::new(); + diagnostics + .into_iter() + .filter(|diagnostic| { + diagnostic.code != LINT_UNAVAILABLE_CODE || warned.insert(diagnostic.file) + }) + .collect() +} + +/// The warning that a body's SQL was not linted because the engine cannot parse its dialect. +fn unavailable_warning( + reason: &str, + body: &LintSqlBody, + file: &AuthoredFile<'_>, +) -> AuthoredDiagnostic { + let (line, column): (i64, i64) = file.position(body.body_start); + AuthoredDiagnostic { + file: body.file, + line, + column, + code: LINT_UNAVAILABLE_CODE.to_string(), + message: reason.to_string(), + fault: false, + end: None, + remediation: LINT_UNAVAILABLE_REMEDIATION.to_string(), + fix: None, + fix_unavailable_reason: None, } } diff --git a/crates/sqlbuild-rules/src/lint_text/constants.rs b/crates/sqlbuild-rules/src/lint_text/constants.rs index ea4e21a0bf..adc92a97cb 100644 --- a/crates/sqlbuild-rules/src/lint_text/constants.rs +++ b/crates/sqlbuild-rules/src/lint_text/constants.rs @@ -106,6 +106,10 @@ pub(crate) const CTE_ARGUMENT: &str = "__cte"; /// The help for mixed qualification in a SQL macro body, where slot reads are qualified through an alias. pub(crate) const MACRO_MIXED_QUALIFICATION_REMEDIATION: &str = "Use one consistent qualification style in this query scope. A plain slot read such as `@rel.column` counts as unqualified; to qualify it, read through an alias: `FROM @rel AS s`, then `s.@rel.column`."; pub(crate) const NATIVE_LINT_ERROR_CODE: &str = "L003"; +/// The warning code for SQL the native lint engine cannot parse in the project's dialect. +pub(crate) const LINT_UNAVAILABLE_CODE: &str = "L007"; +pub(crate) const LINT_UNAVAILABLE_REMEDIATION: &str = + "This SQL was not linted. Review it by hand until SQL lint supports this dialect."; pub(crate) const PARSE_ERROR_POSITION_PATTERN: &str = r"^Parse error at line (?P\d+), column (?P\d+):"; pub(crate) const HARNESS_CTE_PREFIXES: [&str; 13] = [ @@ -152,6 +156,8 @@ pub(crate) const NOT_AN_OBJECT: &str = "native lint diagnostic must be an object pub(crate) const NON_OBJECT_RESPONSE: &str = "native lint engine returned a non-object response"; pub(crate) const UNSUPPORTED_VERSION: &str = "native lint engine returned an unsupported response version"; +pub(crate) const INVALID_UNAVAILABLE: &str = + "native lint response has a non-string unavailable reason"; pub(crate) const MISSING_DIAGNOSTICS: &str = "native lint response is missing a diagnostics list"; pub(crate) const INVALID_BATCH: &str = "native lint engine returned an invalid batch response"; pub(crate) const NON_CONTIGUOUS_FIX: &str = diff --git a/crates/sqlbuild-rules/src/lint_text/main/counts_toward_exit.rs b/crates/sqlbuild-rules/src/lint_text/main/counts_toward_exit.rs new file mode 100644 index 0000000000..6fcb69d909 --- /dev/null +++ b/crates/sqlbuild-rules/src/lint_text/main/counts_toward_exit.rs @@ -0,0 +1,8 @@ +//! `counts_toward_exit`: whether a lint finding can make a command fail. + +use crate::lint_text::constants::LINT_UNAVAILABLE_CODE; + +/// Whether a finding with `code` counts toward a command's exit status; SQL lint being unavailable for a dialect never does. +pub fn counts_toward_exit(code: &str) -> bool { + code != LINT_UNAVAILABLE_CODE +} diff --git a/crates/sqlbuild-rules/src/lint_text/main/lint_sql_bodies.rs b/crates/sqlbuild-rules/src/lint_text/main/lint_sql_bodies.rs index be43f45778..5eefd1e8f0 100644 --- a/crates/sqlbuild-rules/src/lint_text/main/lint_sql_bodies.rs +++ b/crates/sqlbuild-rules/src/lint_text/main/lint_sql_bodies.rs @@ -5,12 +5,12 @@ use std::collections::HashMap; use serde_json::Value; use crate::lint_text::_helpers::sql_diagnostics::{ - AuthoredFile, authored_diagnostic, parse_failure, response_diagnostics, without_generated_frame, + AuthoredFile, LintResult, body_diagnostics, response_diagnostics, unavailable_once_per_file, }; use crate::lint_text::_helpers::sql_requests::{lint_payload, omitted_select, run_batch}; use crate::lint_text::models::{AuthoredDiagnostic, LintSqlBody, LintSqlOptions}; -/// Lint every body once per distinct request and map each diagnostic onto its authored file. +/// Lint each distinct request once and map its diagnostics, or one unavailable warning per file, onto source. pub fn lint_sql_bodies( bodies: &[LintSqlBody], files: &[&str], @@ -30,31 +30,20 @@ pub fn lint_sql_bodies( } request_of_body.push(index); } - let responses: Vec, String>> = run_batch(&payloads) + let responses: Vec> = run_batch(&payloads) .map(|responses| responses.iter().map(response_diagnostics).collect()) .unwrap_or_else(|error| vec![Err(error); payloads.len()]); let authored: Vec> = files .iter() .map(|text| AuthoredFile::new(text, &options.dialect)) .collect(); - let mut diagnostics: Vec = Vec::new(); - for ((body, offset), request) in bodies.iter().zip(omitted).zip(request_of_body) { - let file: &AuthoredFile<'_> = &authored[body.file]; - let raw: Vec = match &responses[request] { - Ok(raw) => raw.clone(), - Err(message) => { - diagnostics.push(parse_failure(message, body, file)); - continue; - } - }; - let raw: Vec = without_generated_frame(raw, body, offset); - for item in &raw { - match authored_diagnostic(item, body, file) { - Ok(Some(diagnostic)) => diagnostics.push(diagnostic), - Ok(None) => {} - Err(message) => diagnostics.push(parse_failure(&message, body, file)), - } - } - } - diagnostics + let diagnostics: Vec = bodies + .iter() + .zip(omitted) + .zip(request_of_body) + .flat_map(|((body, offset), request)| { + body_diagnostics(&responses[request], (body, offset), &authored[body.file]) + }) + .collect(); + unavailable_once_per_file(diagnostics) } diff --git a/crates/sqlbuild-rules/src/lint_text/main/mod.rs b/crates/sqlbuild-rules/src/lint_text/main/mod.rs index 3343ef6e90..b3640027a2 100644 --- a/crates/sqlbuild-rules/src/lint_text/main/mod.rs +++ b/crates/sqlbuild-rules/src/lint_text/main/mod.rs @@ -1,3 +1,4 @@ +pub mod counts_toward_exit; pub mod defined_cte_names; pub mod dependency_sites; pub mod first_header_only; diff --git a/crates/sqlbuild-rules/src/lint_text/tests/test_lint_text.rs b/crates/sqlbuild-rules/src/lint_text/tests/test_lint_text.rs index 454ce07874..0142637b30 100644 --- a/crates/sqlbuild-rules/src/lint_text/tests/test_lint_text.rs +++ b/crates/sqlbuild-rules/src/lint_text/tests/test_lint_text.rs @@ -1,8 +1,12 @@ use crate::lint_text::_helpers::format_bodies::unwrapped_expression; use crate::lint_text::_helpers::sites::site_pattern; -use crate::lint_text::_helpers::sql_diagnostics::interpolation_text_at; +use crate::lint_text::_helpers::sql_diagnostics::{ + AuthoredFile, body_diagnostics, interpolation_text_at, response_diagnostics, + unavailable_once_per_file, +}; use crate::lint_text::constants::SITE_START_BYTES; use crate::lint_text::errors::LintTextError; +use crate::lint_text::main::counts_toward_exit::counts_toward_exit; use crate::lint_text::main::format_sql_bodies::format_sql_bodies; use crate::lint_text::main::frame_sql_macro_expression::frame_sql_macro_expression; use crate::lint_text::main::lint_sql_bodies::lint_sql_bodies; @@ -16,9 +20,9 @@ use crate::lint_text::main::restore_interpolation::restore_interpolation; use crate::lint_text::models::{ExpansionSpan, FormatFile, LintSqlBody, LintSqlOptions}; use crate::lint_text::tests::helpers::directive_finding; use crate::lint_text::tests::test_types::{ - Finding, FormatBodiesTestCase, LintBodiesTestCase, NeutralizeTestCase, ParameterTestCase, - RestoreTestCase, SitePatternTestCase, SlotReadTestCase, SuppressionTestCase, TokenAtTestCase, - UnwrappedExpressionTestCase, + ExitCountTestCase, Finding, FormatBodiesTestCase, LintBodiesTestCase, NeutralizeTestCase, + ParameterTestCase, RestoreTestCase, SitePatternTestCase, SlotReadTestCase, SuppressionTestCase, + TokenAtTestCase, UnavailableFinding, UnavailableLintTestCase, UnwrappedExpressionTestCase, }; use sqlbuild_analysis::sql_macros::main::parse_sql_macro::parse_sql_macro; @@ -666,3 +670,97 @@ fn given_sql_macro_bodies_when_linting_then_frames_and_generated_comments_are_no ); } } + +#[test] +fn given_engine_response_when_dialect_is_unavailable_then_each_file_gets_one_warning() { + const REASON: &str = + "SQL lint is unavailable for the tsql dialect: SQLQuack does not parse it yet"; + let test_cases = [ + UnavailableLintTestCase { + description: "an unavailable reason warns once at the file's first body", + response: serde_json::json!({"Ok": {"version": 1, "diagnostics": [], "unavailable": REASON}}), + bodies_in_file: 2, + expected_findings: vec![("L007", 2, 1, false, REASON)], + }, + UnavailableLintTestCase { + description: "a linted response without the key keeps its empty diagnostics", + response: serde_json::json!({"Ok": {"version": 1, "diagnostics": []}}), + bodies_in_file: 2, + expected_findings: Vec::new(), + }, + UnavailableLintTestCase { + description: "a non-string reason is an invalid response, not a clean one", + response: serde_json::json!({"Ok": {"version": 1, "diagnostics": [], "unavailable": null}}), + bodies_in_file: 1, + expected_findings: vec![( + "L003", + 2, + 1, + true, + "Native SQL parser could not analyze this body: native lint response has a non-string unavailable reason", + )], + }, + ]; + let file = AuthoredFile::new("MODEL ();\nselect a from t\n", "tsql"); + let body = LintSqlBody { + body_start: 10, + lint_text: "select a from t".to_string(), + ..LintSqlBody::default() + }; + + for test_case in test_cases { + let response = response_diagnostics(&test_case.response); + let diagnostics = unavailable_once_per_file( + (0..test_case.bodies_in_file) + .flat_map(|_| body_diagnostics(&response, (&body, None), &file)) + .collect(), + ); + let findings: Vec> = diagnostics + .iter() + .map(|finding| { + ( + finding.code.as_str(), + finding.line, + finding.column, + finding.fault, + finding.message.as_str(), + ) + }) + .collect(); + + assert_eq!( + findings, test_case.expected_findings, + "{}", + test_case.description + ); + } +} + +#[test] +fn given_findings_when_deciding_exit_then_only_lint_unavailable_never_fails_a_command() { + let test_cases = [ + ExitCountTestCase { + description: "an unavailable-dialect warning reports our gap, not the user's SQL", + code: "L007", + expected_counts: false, + }, + ExitCountTestCase { + description: "a SQL lint rule finding still fails", + code: "SQLCLEAN005", + expected_counts: true, + }, + ExitCountTestCase { + description: "an engine fault still fails", + code: "L003", + expected_counts: true, + }, + ]; + for test_case in test_cases { + assert_eq!( + counts_toward_exit(test_case.code), + test_case.expected_counts, + "{}", + test_case.description + ); + } +} diff --git a/crates/sqlbuild-rules/src/lint_text/tests/test_types.rs b/crates/sqlbuild-rules/src/lint_text/tests/test_types.rs index 60c28b778c..94f883b554 100644 --- a/crates/sqlbuild-rules/src/lint_text/tests/test_types.rs +++ b/crates/sqlbuild-rules/src/lint_text/tests/test_types.rs @@ -56,6 +56,22 @@ pub(crate) struct LintBodiesTestCase { pub(crate) expected_findings: Vec>, } +pub(crate) struct ExitCountTestCase { + pub(crate) description: &'static str, + pub(crate) code: &'static str, + pub(crate) expected_counts: bool, +} + +pub(crate) struct UnavailableLintTestCase { + pub(crate) description: &'static str, + pub(crate) response: serde_json::Value, + pub(crate) bodies_in_file: usize, + pub(crate) expected_findings: Vec>, +} + +/// One authored finding: code, line, column, whether it is a fault, and its message. +pub(crate) type UnavailableFinding<'a> = (&'a str, i64, i64, bool, &'a str); + /// One mapped finding: code, line, column and optional end position. pub(crate) type Finding<'a> = (&'a str, i64, i64, Option<(i64, i64)>); diff --git a/crates/sqlbuild-rules/src/query_facts/_helpers/facts.rs b/crates/sqlbuild-rules/src/query_facts/_helpers/facts.rs index 5af9afca09..a8d3b8e977 100644 --- a/crates/sqlbuild-rules/src/query_facts/_helpers/facts.rs +++ b/crates/sqlbuild-rules/src/query_facts/_helpers/facts.rs @@ -1,53 +1,64 @@ use crate::query_facts::_helpers::shape::stub_call; use crate::query_facts::_helpers::tokens::{ComparisonOperator, QueryTokens, SelectTokens}; use crate::query_facts::constants::{ - MAX_FUNCTION_CALL_DEPTH, TABLE_FUNCTION_STUB, TRIPLE_QUOTE_LENGTH, + MAX_AST_DEPTH, MAX_FUNCTION_CALL_DEPTH, SNOWFLAKE_DATE_PART_ARGUMENTS, TABLE_FUNCTION_STUB, + TRIPLE_QUOTE_LENGTH, }; use crate::query_facts::models::{ CallKind, ColumnFact, ComparisonFacts, ComparisonSpan, CteFacts, ExpressionItem, ItemFacts, ModelShape, ParsedFixture, QueryFacts, RelationFacts, SelectFacts, SetFacts, SourceText, }; -use polyglot_sql::dialects::Dialect; -use polyglot_sql::expressions::{Identifier, Literal, Select, TableRef, With}; -use polyglot_sql::{ComplexityGuardOptions, Expression, ExpressionWalk, ParseOptions}; -use sqlbuild_analysis::lineage::main::parser_dialect::parser_dialect; +use sqlbuild_analysis::sql_tree::main::deeper_than::deeper_than; +use sqlbuild_analysis::sql_tree::main::shared_dialect::shared_dialect; +use sqlquack_syntax::expressions::names::models::{Identifier, Literal, TableRef}; +use sqlquack_syntax::expressions::queries::models::{Select, With}; +use sqlquack_syntax::{ + CallMode, ComplexityGuardOptions, Dialect, Expression, ExpressionWalk, ParseOptions, +}; use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet}; use std::ops::Range; fn parse_query(sql: &str, dialect_name: &str) -> Result<(Expression, Dialect), String> { - let dialect_type = parser_dialect(Some(dialect_name)) - .map_err(|unknown| format!("unknown dialect {unknown}"))?; - let dialect = Dialect::get(dialect_type); + let dialect = shared_dialect(dialect_name) + .ok_or_else(|| format!("no SQL parser for dialect {dialect_name}"))?; let options = ParseOptions { complexity_guard: Some(ComplexityGuardOptions { max_function_call_depth: Some(MAX_FUNCTION_CALL_DEPTH), ..Default::default() }), }; - let mut statements = dialect - .parse_with_options(sql, &options) - .map_err(|error| error.to_string())?; + let mut statements = + sqlquack_syntax::parse_statements_with(sql, dialect, &options, CallMode::FaithfulLocated) + .map_err(|error| error.to_string())?; if statements.len() != 1 { return Err("expected one statement".to_owned()); } let statement = statements.remove(0); + if deeper_than(&statement, MAX_AST_DEPTH) { + return Err(format!( + "E_GUARD_AST_DEPTH_EXCEEDED: value {} exceeds configured limit {MAX_AST_DEPTH}", + MAX_AST_DEPTH + 1 + )); + } if !is_query(&statement) { return Err("expected a query".to_owned()); } Ok((statement, dialect)) } +fn code_tokens(sql: &str, dialect: Dialect) -> Result, String> { + let tokens = sqlquack_syntax::tokenize(sql, dialect).map_err(|error| error.to_string())?; + Ok(QueryTokens::new(tokens, sql)) +} + /// Parse a stubbed SQL-test fixture as one query. pub(crate) fn parse_fixture( shape: ModelShape, dialect_name: &str, ) -> Result { let (statement, dialect) = parse_query(&shape.sql, dialect_name)?; - let tokens = dialect - .tokenize(&shape.sql) - .map_err(|error| error.to_string())?; - let tokens = QueryTokens::new(tokens, &shape.sql); + let tokens = code_tokens(&shape.sql, dialect)?; let selects = tokens.selects(); let order = tokens.top_level_word("ORDER").unwrap_or(tokens.len()); let order_end = ["LIMIT", "OFFSET", "FETCH"] @@ -72,11 +83,10 @@ pub(crate) fn parse_fixture( }) } -/// Parse normalized model SQL with Polyglot and read the facts built-in rules evaluate. +/// Parse normalized model SQL with SQLQuack and read the facts built-in rules evaluate. pub(crate) fn query_facts(sql: &str, dialect_name: &str) -> Result { let (statement, dialect) = parse_query(sql, dialect_name)?; - let tokens = dialect.tokenize(sql).map_err(|error| error.to_string())?; - let tokens = QueryTokens::new(tokens, sql); + let tokens = code_tokens(sql, dialect)?; let builder = Builder { selects: tokens.selects(), tokens, @@ -304,16 +314,14 @@ impl Builder<'_> { let unwrapped = unwrap_paren(value); let cast_type = match unwrapped { Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => { - Some( - range - .as_ref() - .and_then(|range| self.tokens.cast_type(range)) - .unwrap_or_else(|| { - self.dialect - .generate(&Expression::DataType(cast.to.clone())) - .unwrap_or_default() - }), - ) + range + .as_ref() + .and_then(|range| self.tokens.cast_type(range)) + .or_else(|| { + let span = cast.span.0?; + self.tokens + .cast_type(&self.tokens.covering(span.start, span.end)) + }) } _ => None, }; @@ -361,15 +369,19 @@ impl Builder<'_> { if let Some(span) = self.spans.borrow().get(&key) { return *span; } - let anchors = anchors(parts); - let generated = if anchors == (None, None) { - self.dialect.generate(node).unwrap_or_default() - } else { - String::new() + let anchors = match anchors(parts) { + (None, None) => ( + parts.operands[0] + .source_span() + .map(|span| span.end) + .or_else(|| node.source_span().map(|span| span.start)), + None, + ), + anchors => anchors, }; let span = self .tokens - .comparison_range(parts.operator, anchors, parts.negated, &generated); + .comparison_range(parts.operator, anchors, parts.negated); self.spans.borrow_mut().insert(key, span); span } @@ -404,7 +416,10 @@ impl Builder<'_> { let mut fact = ComparisonFacts { span, generated_sql: if span.is_none() { - self.dialect.generate(node).unwrap_or_default() + node.source_span() + .and_then(|span| self.tokens.slice(span.start, span.end)) + .unwrap_or_default() + .to_owned() } else { String::new() }, @@ -420,6 +435,7 @@ impl Builder<'_> { let mut string_tokens: Vec<(usize, usize, &str)> = span.map_or_else(Vec::new, |span| { self.tokens.string_tokens(span.start, span.end) }); + let units = date_part_arguments(node, self.dialect); for value in node.dfs() { match value { Expression::Column(column) => { @@ -428,11 +444,20 @@ impl Builder<'_> { Expression::Identifier(identifier) => { fact.columns.insert(identifier.name.clone()); } + Expression::Literal(literal) if units.contains(&std::ptr::from_ref(value)) => { + if let Some(unit) = string_literal(literal) + && let Some(index) = + string_tokens.iter().position(|(_, _, text)| *text == unit) + { + string_tokens.remove(index); + } + } Expression::Literal(literal) => { if let Some(generated) = string_literal(literal) { let position = string_tokens .iter() - .position(|(_, _, text)| *text == literal.value_str()); + .position(|(_, _, text)| *text == generated) + .or_else(|| (!string_tokens.is_empty()).then_some(0)); let range = position.map(|index| { let (start, end, _) = string_tokens.remove(index); (start, end) @@ -452,9 +477,10 @@ impl Builder<'_> { .extend(operand.dfs().filter_map(direct_column)); } if direct_string_literal(operand).is_none() - && operand - .dfs() - .any(|value| direct_string_literal(value).is_some()) + && operand.dfs().any(|value| { + !units.contains(&std::ptr::from_ref(value)) + && direct_string_literal(value).is_some() + }) { fact.modified_string_literal = true; } @@ -463,6 +489,31 @@ impl Builder<'_> { } } +/// The quoted date-part arguments of Snowflake calls such as `DATEDIFF('day', a, b)`. +fn date_part_arguments(root: &Expression, dialect: Dialect) -> HashSet<*const Expression> { + if dialect != Dialect::Snowflake { + return HashSet::new(); + } + root.dfs().filter_map(date_part_argument).collect() +} + +/// The quoted date-part argument of `node` when it is a Snowflake date-part call. +fn date_part_argument(node: &Expression) -> Option<*const Expression> { + let Expression::Function(function) = node else { + return None; + }; + let name = function.name.to_ascii_uppercase(); + for (call, count, index) in SNOWFLAKE_DATE_PART_ARGUMENTS { + if call == name && count == function.args.len() { + let argument = function.args.get(index)?; + return direct_string_literal(argument) + .is_some() + .then(|| std::ptr::from_ref(argument)); + } + } + None +} + fn comparison_parts(node: &Expression) -> Option> { let (operator, operands, negated) = match node { Expression::Eq(op) => (ComparisonOperator::Eq, vec![&op.left, &op.right], false), @@ -668,7 +719,7 @@ fn own_span(expression: &Expression) -> Option { Expression::Column(column) => column.span.or(column.name.span), Expression::Identifier(identifier) => identifier.span, Expression::Star(star) => star.table.as_ref().and_then(|table| table.span), - Expression::Function(function) => function.span, + Expression::Function(function) => function.span.0, Expression::Table(table) => table .catalog .as_ref() @@ -692,7 +743,7 @@ fn span_bounds(expression: &Expression) -> Option<(usize, usize)> { Expression::Column(column) => column.span.or(column.name.span), Expression::Identifier(identifier) => identifier.span, Expression::Star(star) => star.table.as_ref().and_then(|table| table.span), - Expression::Function(function) => function.span, + Expression::Function(function) => function.span.0, Expression::Count(count) => count.span, Expression::Table(table) => table.name.span, _ => None, diff --git a/crates/sqlbuild-rules/src/query_facts/_helpers/shape.rs b/crates/sqlbuild-rules/src/query_facts/_helpers/shape.rs index 0f2b3ea81d..f5dbfd7b6e 100644 --- a/crates/sqlbuild-rules/src/query_facts/_helpers/shape.rs +++ b/crates/sqlbuild-rules/src/query_facts/_helpers/shape.rs @@ -1,12 +1,15 @@ use crate::query_facts::constants::{ - CALL_STUB, PLACEHOLDER_PREFIX, PLACEHOLDER_VALUE, REF_STUB, SOURCE_STUB, TABLE_FUNCTION_CALL, - TABLE_FUNCTION_STUB, UDF_CALL, UDF_STUB, + CALL_STUB, PLACEHOLDER_PREFIX, PLACEHOLDER_VALUE, REF_STUB, SNOWFLAKE_DIALECT, SOURCE_STUB, + TABLE_FUNCTION_CALL, TABLE_FUNCTION_STUB, UDF_CALL, UDF_STUB, }; use crate::query_facts::models::{CallKind, ModelCall, ModelShape}; use crate::rules::main::compact_sql::compact_sql; use sqlbuild_analysis::semantic_validation::main::normalize_mapped::normalize_mapped; +use sqlbuild_sqltext::sql_scan::constants::TABLE_WRAPPER; +use sqlbuild_sqltext::sql_scan::main::dialect_lexical_syntax::dialect_lexical_syntax; use sqlbuild_sqltext::sql_scan::main::dialect_matching_paren::dialect_matching_paren; use sqlbuild_sqltext::sql_scan::main::dialect_non_code_end::dialect_non_code_end; +use sqlbuild_sqltext::sql_scan::main::follows_table_wrapper::follows_table_wrapper; use sqlbuild_sqltext::sql_scan::main::quote_policy::quote_policy; use sqlbuild_sqltext::sql_scan::models::{LexicalSyntax, QuotePolicy}; @@ -15,7 +18,12 @@ pub(crate) fn model_shape(sql: &str, dialect: &str) -> Result = normalized @@ -77,46 +85,6 @@ fn is_word_byte(byte: u8) -> bool { byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'$' } -/// The comment and quoted-text syntax the rules parser tokenized for `dialect`. -fn lexical_syntax(dialect: &str) -> LexicalSyntax { - let strings = |quotes: &[&str]| quotes.iter().map(|quote| (*quote).to_owned()).collect(); - let lines = |prefixes: &[&str]| prefixes.iter().map(|prefix| (*prefix).to_owned()).collect(); - match dialect.to_ascii_lowercase().as_str() { - "bigquery" => LexicalSyntax { - backslash_escape_quotes: strings(&["'", "\""]), - raw_string_prefix: true, - triple_quoted_strings: true, - line_comment_prefixes: lines(&["--", "#"]), - ..LexicalSyntax::default() - }, - "snowflake" => LexicalSyntax { - backslash_escape_quotes: strings(&["'"]), - line_comment_prefixes: lines(&["--", "//"]), - ..LexicalSyntax::default() - }, - "clickhouse" => LexicalSyntax { - backslash_escape_quotes: strings(&["'"]), - line_comment_prefixes: lines(&["--", "#", "//"]), - ..LexicalSyntax::default() - }, - "databricks" | "duckdb" => LexicalSyntax { - line_comment_prefixes: lines(&["--"]), - ..LexicalSyntax::default() - }, - "mssql" | "sqlserver" | "tsql" => LexicalSyntax { - nested_block_comments: true, - line_comment_prefixes: lines(&["--"]), - ..LexicalSyntax::default() - }, - _ => LexicalSyntax { - escape_string_prefix: true, - nested_block_comments: true, - line_comment_prefixes: lines(&["--"]), - ..LexicalSyntax::default() - }, - } -} - fn is_quote_start(byte: u8) -> bool { matches!(byte, b'\'' | b'"' | b'`' | b'$') } @@ -157,10 +125,12 @@ fn quoted_name(sql: &str, open: usize) -> Option<(String, usize)> { (valid && sql.as_bytes().get(close) == Some(&b')')).then(|| (name.to_owned(), close)) } +/// `sql` with calls and placeholders stubbed, table functions optionally wrapped in `TABLE( )`. fn stub_calls( sql: &str, policy: QuotePolicy, syntax: &LexicalSyntax, + wrap_table_functions: bool, ) -> (String, Vec, Vec) { let bytes = sql.as_bytes(); let mut edits: Vec<(usize, usize, String)> = Vec::new(); @@ -217,7 +187,25 @@ fn stub_calls( } else { TABLE_FUNCTION_STUB }; - edits.push((start, open, format!("{stub}{name}"))); + let mut wrap_close = None; + if word == TABLE_FUNCTION_CALL + && wrap_table_functions + && !follows_table_wrapper(&bytes[..start]) + { + let Ok(close) = dialect_matching_paren(bytes, open, syntax) else { + continue; + }; + wrap_close = Some(close); + } + let wrapper = if wrap_close.is_some() { + TABLE_WRAPPER + } else { + "" + }; + edits.push((start, open, format!("{wrapper}{stub}{name}"))); + if let Some(close) = wrap_close { + edits.push((close + 1, close + 1, ")".to_owned())); + } index = open; continue; } @@ -240,6 +228,7 @@ fn stub_calls( edits.push((start, close + 1, format!("{}{number}", stub_prefix(kind)))); index = close + 1; } + edits.sort_by_key(|(start, end, _)| (*start, *end)); let (stubbed, offsets) = apply_edits(sql, edits); (stubbed, offsets, calls) } diff --git a/crates/sqlbuild-rules/src/query_facts/_helpers/tokens.rs b/crates/sqlbuild-rules/src/query_facts/_helpers/tokens.rs index 9e6863c110..b847237997 100644 --- a/crates/sqlbuild-rules/src/query_facts/_helpers/tokens.rs +++ b/crates/sqlbuild-rules/src/query_facts/_helpers/tokens.rs @@ -1,6 +1,8 @@ use crate::query_facts::constants::MIN_CAST_TOKENS; -use crate::query_facts::models::ComparisonSpan; -use polyglot_sql::tokens::{Token, TokenType}; +use crate::query_facts::models::{CodeToken, ComparisonSpan}; +use sqlquack_syntax::LosslessToken; +use sqlquack_syntax::TokenKind; +use sqlquack_syntax::tokens::models::TokenType; use std::cell::RefCell; use std::collections::HashSet; use std::ops::Range; @@ -150,32 +152,35 @@ pub(crate) struct SelectTokens { /// The code tokens of a normalized query, without comments, over its text. pub(crate) struct QueryTokens<'a> { - tokens: Vec, + tokens: Vec>, sql: &'a str, bytes: Vec, used: RefCell>, } impl<'a> QueryTokens<'a> { - pub(crate) fn new(tokens: Vec, sql: &'a str) -> Self { + /// The code tokens of `tokens`, a lossless tokenization of `sql`, at character offsets. + pub(crate) fn new(tokens: Vec, sql: &'a str) -> Self { let mut bytes: Vec = sql.char_indices().map(|(byte, _)| byte).collect(); bytes.push(sql.len()); + let character = |byte: usize| bytes.partition_point(|offset| *offset < byte); + let tokens: Vec> = tokens + .into_iter() + .filter_map(|token| match token.kind { + TokenKind::Significant(token_type) => Some(CodeToken { + token_type, + text: &sql[token.start..token.end], + start: character(token.start), + end: character(token.end), + }), + TokenKind::Whitespace | TokenKind::LineComment | TokenKind::BlockComment => None, + }) + .collect(); Self { sql, bytes, used: RefCell::default(), - tokens: tokens - .into_iter() - .filter(|token| { - !matches!( - token.token_type, - TokenType::BlockComment - | TokenType::LineComment - | TokenType::Space - | TokenType::Break - ) - }) - .collect(), + tokens, } } @@ -185,11 +190,11 @@ impl<'a> QueryTokens<'a> { } pub(crate) fn start(&self, index: usize) -> usize { - self.tokens[index].span.start + self.tokens[index].start } pub(crate) fn end(&self, index: usize) -> usize { - self.tokens[index].span.end + self.tokens[index].end } fn kind(&self, index: usize) -> Option { @@ -369,14 +374,21 @@ impl<'a> QueryTokens<'a> { parameterized && (cast || conversion) } - /// The string-literal tokens within a normalized range, as character range and parsed text. + /// The tokens within characters `start` to `end`, as a token range. + pub(crate) fn covering(&self, start: usize, end: usize) -> Range { + let first = self.tokens.partition_point(|token| token.start < start); + let last = self.tokens.partition_point(|token| token.end <= end); + first..last.max(first) + } + + /// The string-literal tokens within a normalized range, as character range and source text. pub(crate) fn string_tokens(&self, start: usize, end: usize) -> Vec<(usize, usize, &str)> { self.tokens .iter() .filter(|token| { - token.span.start >= start && token.span.end <= end && string_token(token.token_type) + token.start >= start && token.end <= end && string_token(token.token_type) }) - .map(|token| (token.span.start, token.span.end, token.text.as_str())) + .map(|token| (token.start, token.end, token.text)) .collect() } @@ -595,7 +607,6 @@ impl<'a> QueryTokens<'a> { operator: ComparisonOperator, (left_end, right_start): (Option, Option), negated: bool, - generated: &str, ) -> Option { let anchor = match (left_end, right_start) { (Some(end), _) => (0..self.tokens.len()).find(|index| { @@ -608,16 +619,7 @@ impl<'a> QueryTokens<'a> { && self.operator(*index, operator) && !self.used.borrow().contains(index) }), - (None, None) => { - let wanted = compact(generated); - (0..self.tokens.len()).find(|index| { - self.operator(*index, operator) && !self.used.borrow().contains(index) && { - let (first, last) = self.extent(*index, operator, negated); - self.slice(self.start(first), self.end(last)) - .is_some_and(|text| compact(text) == wanted) - } - }) - } + (None, None) => None, }?; self.used.borrow_mut().insert(anchor); let (first, last) = self.extent(anchor, operator, negated); @@ -685,13 +687,6 @@ impl<'a> QueryTokens<'a> { } } -fn compact(text: &str) -> String { - text.chars() - .filter(|character| !character.is_whitespace()) - .flat_map(char::to_lowercase) - .collect() -} - fn string_token(token_type: TokenType) -> bool { matches!( token_type, diff --git a/crates/sqlbuild-rules/src/query_facts/constants.rs b/crates/sqlbuild-rules/src/query_facts/constants.rs index 564143160d..ae3601132a 100644 --- a/crates/sqlbuild-rules/src/query_facts/constants.rs +++ b/crates/sqlbuild-rules/src/query_facts/constants.rs @@ -6,7 +6,18 @@ pub(crate) const UDF_STUB: &str = "__sqlbuild_udf_"; pub(crate) const PLACEHOLDER_PREFIX: &[u8] = b"@@@"; pub(crate) const PLACEHOLDER_VALUE: &str = "NULL"; pub(crate) const MAX_FUNCTION_CALL_DEPTH: usize = 128; +/// The deepest statement built-in rules read, Polyglot's former AST-depth limit. +pub(crate) const MAX_AST_DEPTH: usize = 512; pub(crate) const UDF_CALL: &str = "__udf"; pub(crate) const TABLE_FUNCTION_CALL: &str = "__table_fn"; +pub(crate) const SNOWFLAKE_DIALECT: &str = "snowflake"; pub(crate) const MIN_CAST_TOKENS: usize = 2; pub(crate) const TRIPLE_QUOTE_LENGTH: usize = 3; +/// Snowflake calls with a date-part argument, as (name, argument count, date-part index). +pub(crate) const SNOWFLAKE_DATE_PART_ARGUMENTS: [(&str, usize, usize); 5] = [ + ("DATEDIFF", 3, 0), + ("DATE_DIFF", 3, 0), + ("TIMEDIFF", 3, 0), + ("TIMESTAMPDIFF", 3, 0), + ("LAST_DAY", 2, 1), +]; diff --git a/crates/sqlbuild-rules/src/query_facts/main/facts_available.rs b/crates/sqlbuild-rules/src/query_facts/main/facts_available.rs new file mode 100644 index 0000000000..d4e29aac88 --- /dev/null +++ b/crates/sqlbuild-rules/src/query_facts/main/facts_available.rs @@ -0,0 +1,8 @@ +//! `facts_available`: whether SQLBuild parses a dialect's SQL for built-in rules. + +use sqlbuild_analysis::sql_tree::main::shared_dialect::shared_dialect; + +/// Whether SQLBuild parses `dialect`'s SQL for the built-in rules that read query facts. +pub(crate) fn facts_available(dialect: &str) -> bool { + shared_dialect(dialect).is_some() +} diff --git a/crates/sqlbuild-rules/src/query_facts/main/mod.rs b/crates/sqlbuild-rules/src/query_facts/main/mod.rs index 12999bd6c5..a4b8b0f0c7 100644 --- a/crates/sqlbuild-rules/src/query_facts/main/mod.rs +++ b/crates/sqlbuild-rules/src/query_facts/main/mod.rs @@ -1,4 +1,5 @@ pub mod dependency_texts; +pub mod facts_available; pub mod model_shape; pub mod parse_fixture; pub mod query_facts; diff --git a/crates/sqlbuild-rules/src/query_facts/main/query_facts.rs b/crates/sqlbuild-rules/src/query_facts/main/query_facts.rs index dba714d2c9..e246caab16 100644 --- a/crates/sqlbuild-rules/src/query_facts/main/query_facts.rs +++ b/crates/sqlbuild-rules/src/query_facts/main/query_facts.rs @@ -1,6 +1,6 @@ use crate::query_facts::models::QueryFacts; -/// Parse normalized model SQL with Polyglot and read the facts built-in rules evaluate. +/// Parse normalized model SQL with SQLQuack and read the facts built-in rules evaluate. pub(crate) fn query_facts(sql: &str, dialect: &str) -> Result { crate::query_facts::_helpers::facts::query_facts(sql, dialect) } diff --git a/crates/sqlbuild-rules/src/query_facts/models.rs b/crates/sqlbuild-rules/src/query_facts/models.rs index 35dd961830..a5688f1e06 100644 --- a/crates/sqlbuild-rules/src/query_facts/models.rs +++ b/crates/sqlbuild-rules/src/query_facts/models.rs @@ -1,4 +1,5 @@ -use polyglot_sql::Expression; +use sqlquack_syntax::Expression; +use sqlquack_syntax::tokens::models::TokenType; use std::collections::BTreeSet; use crate::lint_text::main::map_expanded_offset::map_expanded_offset; @@ -192,7 +193,7 @@ impl QueryFacts { } } -/// An SQL-test fixture query parsed with Polyglot, with the function-call facts its rules read. +/// An SQL-test fixture query parsed with SQLQuack, with the function-call facts its rules read. #[derive(Debug)] pub(crate) struct ParsedFixture { pub(crate) statement: Expression, @@ -366,3 +367,12 @@ impl<'a> ModelSource<'a> { self.sql.get(from..to.max(from)).unwrap_or_default() } } + +/// One code token of a normalized query: its type, source text and character range. +#[derive(Debug)] +pub(crate) struct CodeToken<'a> { + pub(crate) token_type: TokenType, + pub(crate) text: &'a str, + pub(crate) start: usize, + pub(crate) end: usize, +} diff --git a/crates/sqlbuild-rules/src/rules/_helpers/evaluation.rs b/crates/sqlbuild-rules/src/rules/_helpers/evaluation.rs index 733cabe15d..4e3256ff5e 100644 --- a/crates/sqlbuild-rules/src/rules/_helpers/evaluation.rs +++ b/crates/sqlbuild-rules/src/rules/_helpers/evaluation.rs @@ -6,9 +6,12 @@ use crate::constants::{ }; use crate::constants::{ MIN_CUSTOM_RULE_TEST_CASES_KEY, MODEL_NAME_LAYERS, PUBLISHED_READ_CODE, PUBLISHED_SHAPE_CODE, + RULES_UNAVAILABLE_REMEDIATION, }; +use crate::lint_text::constants::LINT_UNAVAILABLE_CODE; use crate::models::{Declaration, EvaluateRequest, Fault, Model, RuleMetadata, RulesConfig}; use crate::query_facts::main::dependency_texts::dependency_texts; +use crate::query_facts::main::facts_available::facts_available; use crate::query_facts::main::model_shape::model_shape; use crate::query_facts::main::query_facts::query_facts; use crate::query_facts::models::{ @@ -134,6 +137,17 @@ fn evaluate_model_inner(request: ModelEvaluationRequest<'_>) -> Result) -> faults.into_inner() } +/// One non-failing warning that `dialect` SQL is not parsed, so the built-in rules `codes` did not run on `path`. +pub(crate) fn rules_unavailable<'a>( + path: &str, + codes: impl Iterator, + dialect: &str, +) -> Fault { + Fault { + unevaluated: false, + code: LINT_UNAVAILABLE_CODE.to_owned(), + path: path.to_owned(), + line: 1, + column: 1, + message: format!( + "SQLBuild cannot parse {dialect} SQL for built-in rules yet, so these rules were not evaluated: {}", + codes.collect::>().join(", ") + ), + remediation: RULES_UNAVAILABLE_REMEDIATION.to_owned(), + } +} + fn requires_parsed_model(selected: &BTreeMap) -> bool { selected.values().any(|rule| requires_model_query(rule)) } diff --git a/crates/sqlbuild-rules/src/rules/_helpers/sql_test_rules.rs b/crates/sqlbuild-rules/src/rules/_helpers/sql_test_rules.rs index ae4c1d8d29..f53e86786c 100644 --- a/crates/sqlbuild-rules/src/rules/_helpers/sql_test_rules.rs +++ b/crates/sqlbuild-rules/src/rules/_helpers/sql_test_rules.rs @@ -2,18 +2,20 @@ use crate::models::{ DeclarationKind, EvaluateRequest, Fault, Model, ResourceKind, RuleMetadata, RulesConfig, ScopeResource, SqlScenarioFact, SqlTestCteFact, SqlTestFact, SqlTestMode, }; +use crate::query_facts::main::facts_available::facts_available; use crate::query_facts::main::model_shape::model_shape; use crate::query_facts::main::parse_fixture::parse_fixture; use crate::query_facts::main::stub_call::stub_call; use crate::query_facts::models::{CallKind, ParsedFixture}; -use crate::rules::_helpers::evaluation::path_fault; +use crate::rules::_helpers::evaluation::{path_fault, rules_unavailable}; use crate::rules::models::ProjectEvaluationRequest; -use polyglot_sql::Expression; -use polyglot_sql::expressions::{Literal, Select}; use serde::{Deserialize, Serialize}; use sqlbuild_analysis::compiler::main::sql_test_fixture_facts::fixture_facts; +use sqlquack_syntax::Expression; +use sqlquack_syntax::expressions::names::models::Literal; +use sqlquack_syntax::expressions::queries::models::Select; use std::cell::RefCell; -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::path::Path; use std::rc::Rc; @@ -42,17 +44,35 @@ fn evaluated_project(evaluation: ProjectEvaluationRequest<'_>) -> Result> = HashMap::new(); - for rule in evaluation.selected.values().filter(|rule| { - matches!( - rule.code.as_str(), - "SQBRTEST201" | "SQBRTEST202" | EMPTY_INPUT_RULE_CODE - ) - }) { + let fixture_rules: Vec<&RuleMetadata> = evaluation + .selected + .values() + .copied() + .filter(|rule| { + matches!( + rule.code.as_str(), + "SQBRTEST201" | "SQBRTEST202" | EMPTY_INPUT_RULE_CODE + ) + }) + .collect(); + let dialect: &str = &evaluation.request.dialect; + if !fixture_rules.is_empty() && !facts_available(dialect) { + let paths: BTreeSet<&str> = tests + .iter() + .filter(|test| reads_fixtures(test)) + .map(|test| test.source_path.as_str()) + .collect(); + let codes: Vec<&str> = fixture_rules + .iter() + .map(|rule| rule.code.as_str()) + .collect(); + for path in paths { + faults.push(rules_unavailable(path, codes.iter().copied(), dialect)); + } + } + for rule in fixture_rules.iter().filter(|_| facts_available(dialect)) { for test in &tests { - if !matches!(test.mode, SqlTestMode::Model) - || test.has_macro_mocks - || test.has_model_query_overrides - { + if !reads_fixtures(test) { continue; } for cte in test @@ -114,6 +134,13 @@ fn evaluated_project(evaluation: ProjectEvaluationRequest<'_>) -> Result bool { + matches!(test.mode, SqlTestMode::Model) + && !test.has_macro_mocks + && !test.has_model_query_overrides +} + fn canonical_roots( rule: &RuleMetadata, tests: &[&SqlTestFact], diff --git a/crates/sqlbuild-sqltext/src/sql_scan/constants.rs b/crates/sqlbuild-sqltext/src/sql_scan/constants.rs index f7d11818a4..ffcd1a72a2 100644 --- a/crates/sqlbuild-sqltext/src/sql_scan/constants.rs +++ b/crates/sqlbuild-sqltext/src/sql_scan/constants.rs @@ -14,3 +14,5 @@ pub const GENERIC_LINE_COMMENT_PREFIX: &str = "--"; pub const TRIPLE_QUOTES: [&str; 2] = ["'''", "\"\"\""]; pub const ESCAPE_CHARACTER: char = '\\'; pub const BLOCK_COMMENT_OPEN: &str = "/*"; +/// The wrapper Snowflake takes around a table function call in FROM. +pub const TABLE_WRAPPER: &str = "TABLE("; diff --git a/crates/sqlbuild-sqltext/src/sql_scan/main/dialect_lexical_syntax.rs b/crates/sqlbuild-sqltext/src/sql_scan/main/dialect_lexical_syntax.rs new file mode 100644 index 0000000000..ff94e94b32 --- /dev/null +++ b/crates/sqlbuild-sqltext/src/sql_scan/main/dialect_lexical_syntax.rs @@ -0,0 +1,43 @@ +//! `dialect_lexical_syntax`: the comment and quoted-text syntax of a dialect's SQL, by dialect name. + +use crate::sql_scan::models::LexicalSyntax; + +/// The comment and quoted-text syntax that SQLQuack tokenizes for `dialect`. +pub fn dialect_lexical_syntax(dialect: &str) -> LexicalSyntax { + let strings = |quotes: &[&str]| quotes.iter().map(|quote| (*quote).to_owned()).collect(); + let lines = |prefixes: &[&str]| prefixes.iter().map(|prefix| (*prefix).to_owned()).collect(); + match dialect.to_ascii_lowercase().as_str() { + "bigquery" => LexicalSyntax { + backslash_escape_quotes: strings(&["'", "\""]), + raw_string_prefix: true, + triple_quoted_strings: true, + line_comment_prefixes: lines(&["--", "#"]), + ..LexicalSyntax::default() + }, + "snowflake" => LexicalSyntax { + backslash_escape_quotes: strings(&["'"]), + line_comment_prefixes: lines(&["--", "//"]), + ..LexicalSyntax::default() + }, + "clickhouse" => LexicalSyntax { + backslash_escape_quotes: strings(&["'"]), + line_comment_prefixes: lines(&["--", "#", "//"]), + ..LexicalSyntax::default() + }, + "databricks" | "duckdb" => LexicalSyntax { + line_comment_prefixes: lines(&["--"]), + ..LexicalSyntax::default() + }, + "mssql" | "sqlserver" | "tsql" => LexicalSyntax { + nested_block_comments: true, + line_comment_prefixes: lines(&["--"]), + ..LexicalSyntax::default() + }, + _ => LexicalSyntax { + escape_string_prefix: true, + nested_block_comments: true, + line_comment_prefixes: lines(&["--"]), + ..LexicalSyntax::default() + }, + } +} diff --git a/crates/sqlbuild-sqltext/src/sql_scan/main/follows_table_wrapper.rs b/crates/sqlbuild-sqltext/src/sql_scan/main/follows_table_wrapper.rs new file mode 100644 index 0000000000..5eca2c2433 --- /dev/null +++ b/crates/sqlbuild-sqltext/src/sql_scan/main/follows_table_wrapper.rs @@ -0,0 +1,20 @@ +//! Whether SQL text ends inside an open `TABLE(` wrapper. + +use crate::sql_scan::constants::TABLE_WRAPPER; + +/// Whether `before`, ignoring trailing ASCII whitespace, ends with the word `TABLE` and `(`. +#[must_use] +pub fn follows_table_wrapper(before: &[u8]) -> bool { + let Some(before) = before.trim_ascii_end().strip_suffix(b"(") else { + return false; + }; + let before = before.trim_ascii_end(); + let keyword = &TABLE_WRAPPER.as_bytes()[..TABLE_WRAPPER.len() - 1]; + let Some(start) = before.len().checked_sub(keyword.len()) else { + return false; + }; + before[start..].eq_ignore_ascii_case(keyword) + && start.checked_sub(1).is_none_or(|index| { + !(before[index].is_ascii_alphanumeric() || matches!(before[index], b'_' | b'$')) + }) +} diff --git a/crates/sqlbuild-sqltext/src/sql_scan/main/mod.rs b/crates/sqlbuild-sqltext/src/sql_scan/main/mod.rs index 4735e4b172..b75cc2205a 100644 --- a/crates/sqlbuild-sqltext/src/sql_scan/main/mod.rs +++ b/crates/sqlbuild-sqltext/src/sql_scan/main/mod.rs @@ -1,7 +1,9 @@ pub mod comment_end; +pub mod dialect_lexical_syntax; pub mod dialect_matching_paren; pub mod dialect_non_code_end; pub mod dialect_non_code_ranges; +pub mod follows_table_wrapper; pub mod lexical_syntax_key; pub mod matching_paren; pub mod non_code_end; diff --git a/fensu.toml b/fensu.toml index 7c1f8e7b53..55500200d9 100644 --- a/fensu.toml +++ b/fensu.toml @@ -1381,7 +1381,7 @@ paths = ["crates/sqlbuild-orchestration/src/api/_helpers/resources/administrator reason = "Each function is one utoipa-annotated axum route: OpenAPI needs one annotated handler per operation, and each body only checks the caller's scope and calls one store operation. Scope checks and store work live in their shared owners." [[dupes.allowlist]] -paths = ["crates/sqlbuild-orchestration/src/authorization/main/read_security_audit.rs", "crates/sqlbuild-orchestration/src/credentials/_helpers/active_session.rs", "crates/sqlbuild-orchestration/src/declarations/_helpers/reads.rs", "crates/sqlbuild-orchestration/src/declarations/main/list_person_jobs.rs", "crates/sqlbuild-orchestration/src/declarations/main/list_person_triggers.rs", "crates/sqlbuild-orchestration/src/declarations/main/list_triggers.rs", "crates/sqlbuild-orchestration/src/platform/main/read_location_work.rs", "crates/sqlbuild-orchestration/src/runs/main/list_person_runs.rs", "crates/sqlbuild-orchestration/src/runs/main/list_person_run_nodes.rs", "crates/sqlbuild-orchestration/src/runs/main/list_run_nodes.rs", "crates/sqlbuild-orchestration/src/runs/main/list_runs.rs", "crates/sqlbuild-orchestration/src/runs/_helpers/lifecycle/reads.rs", "crates/sqlbuild-orchestration/src/warehouse_history/_helpers/checks.rs", "crates/sqlbuild-orchestration/src/scheduler/main/list_person_ticks.rs", "crates/sqlbuild-orchestration/src/store/main/read_records.rs", "crates/sqlbuild-orchestration/src/platform/main/location_work.rs", "crates/sqlbuild-orchestration/src/scheduler/main/list_ticks.rs", "crates/sqlbuild-orchestration/src/sensors/_helpers/attempts.rs", "crates/sqlbuild-orchestration/src/sensors/_helpers/facts.rs", "crates/sqlbuild-orchestration/src/snapshots/main/read_current_record.rs", "crates/sqlbuild-orchestration/src/snapshots/main/read_snapshot_documents.rs", "crates/sqlbuild-orchestration/src/snapshots/main/read_snapshot_nodes.rs", "crates/sqlbuild-orchestration/src/warehouse_metadata/main/read_person_catalog.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_check.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_checks.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_maintenance.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_microbatch_coverage.rs", "crates/sqlbuild-orchestration/src/warehouse_metadata/main/read_person_statistics.rs"] +paths = ["crates/sqlbuild-orchestration/src/authorization/main/list_role_definitions.rs", "crates/sqlbuild-orchestration/src/authorization/main/read_security_audit.rs", "crates/sqlbuild-orchestration/src/credentials/_helpers/active_session.rs", "crates/sqlbuild-orchestration/src/declarations/_helpers/reads.rs", "crates/sqlbuild-orchestration/src/declarations/main/list_person_jobs.rs", "crates/sqlbuild-orchestration/src/declarations/main/list_person_triggers.rs", "crates/sqlbuild-orchestration/src/declarations/main/list_triggers.rs", "crates/sqlbuild-orchestration/src/platform/main/read_location_work.rs", "crates/sqlbuild-orchestration/src/platform/main/read_person_location_health.rs", "crates/sqlbuild-orchestration/src/platform/main/location_health.rs", "crates/sqlbuild-orchestration/src/snapshots/main/current_snapshot_record.rs", "crates/sqlbuild-orchestration/src/runs/main/list_person_runs.rs", "crates/sqlbuild-orchestration/src/runs/main/list_person_run_nodes.rs", "crates/sqlbuild-orchestration/src/runs/main/list_run_nodes.rs", "crates/sqlbuild-orchestration/src/runs/main/list_runs.rs", "crates/sqlbuild-orchestration/src/runs/_helpers/lifecycle/reads.rs", "crates/sqlbuild-orchestration/src/warehouse_history/_helpers/checks.rs", "crates/sqlbuild-orchestration/src/scheduler/main/list_person_ticks.rs", "crates/sqlbuild-orchestration/src/store/main/read_records.rs", "crates/sqlbuild-orchestration/src/platform/main/location_work.rs", "crates/sqlbuild-orchestration/src/scheduler/main/list_ticks.rs", "crates/sqlbuild-orchestration/src/sensors/_helpers/attempts.rs", "crates/sqlbuild-orchestration/src/sensors/_helpers/facts.rs", "crates/sqlbuild-orchestration/src/snapshots/main/read_current_record.rs", "crates/sqlbuild-orchestration/src/snapshots/main/read_snapshot_documents.rs", "crates/sqlbuild-orchestration/src/snapshots/main/read_snapshot_nodes.rs", "crates/sqlbuild-orchestration/src/warehouse_metadata/main/read_person_catalog.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_check.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_checks.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_maintenance.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_microbatch_coverage.rs", "crates/sqlbuild-orchestration/src/warehouse_metadata/main/read_person_statistics.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_node_runs.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_events.rs", "crates/sqlbuild-orchestration/src/warehouse_history/main/read_person_run_statements.rs", "crates/sqlbuild-orchestration/src/warehouse_history/_helpers/node_runs.rs"] reason = "Each is one control-store read with its own SQL and record type. Transactions, person authorization and decoding go through the shared begin_authorized, query_records, read_records and query_bodies helpers; what remains alike is the call shape." [[dupes.allowlist]] @@ -2439,7 +2439,7 @@ pending_budgets = [ "crates/sqlbuild-python/src/bindings/_helpers/state_rows/state_tables.rs=1", "crates/sqlbuild-python/src/bindings/_helpers/state_rows/timestamps.rs=4", "crates/sqlbuild-python/src/bindings/_helpers/type_system/equality.rs=3", - "crates/sqlbuild-python/src/bindings/_helpers/type_system/normalization.rs=2", + "crates/sqlbuild-python/src/bindings/_helpers/type_system/normalization.rs=0", "crates/sqlbuild-python/src/bindings/_helpers/workspace_commands/commands.rs=5", "crates/sqlbuild-python/src/bindings/models.rs=0", ] @@ -2485,7 +2485,7 @@ pending_budgets = [ "crates/sqlbuild-python/src/bindings/_helpers/clone/clone_progress.rs=3", "crates/sqlbuild-python/src/bindings/_helpers/command_runtime/invocation.rs=4", "crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouse_scope.rs=2", - "crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs=5", + "crates/sqlbuild-python/src/bindings/_helpers/command_runtime/warehouses.rs=4", "crates/sqlbuild-python/src/bindings/_helpers/compile_attachment/sql_expansion_context.rs=5", "crates/sqlbuild-python/src/bindings/_helpers/compile_command/models.rs=9", "crates/sqlbuild-python/src/bindings/_helpers/compile_command/prepared_artifacts.rs=7", @@ -2931,3 +2931,7 @@ reason = "The override keeps queue.Queue.put's positional protocol, which every rule = "FFS201" path = "src/sqlbuild/orchestration_declarations/models/sensor_context.py" reason = "Only SensorContext lives here: invocation-local ctx.cursor assignment proposes progress, committed solely by native fenced completion. The package regression guards this declaration-only scope; class-symbol exceptions are unsupported." + +[[dupes.allowlist]] +paths = ["crates/sqlbuild-analysis/src/warehouse_types/_helpers/type_trees.rs", "crates/sqlbuild-analysis/src/assembly/analysis_session/_helpers/dict_walk.rs", "crates/sqlbuild-analysis/src/relation_names/_helpers/walk.rs"] +reason = "Each is a short stack walk over a JSON syntax tree with its own stop and collect rule: cast_targets collects every cast's target type, branch_selects stops at each set-operation branch, and walked lists every object. A shared visitor would need a callback for each rule and save no logic." diff --git a/python-boundary.toml b/python-boundary.toml index 7b959ef22a..0aa3f9f4ee 100644 --- a/python-boundary.toml +++ b/python-boundary.toml @@ -567,11 +567,6 @@ path = "compiler/auditing/main/get_audit_factory_definition.py" rule = 3 note = "Reads the declaration attached to a project audit factory." -[[stays]] -path = "compiler/sql_analysis/main/import_polyglot.py" -rule = 4 -note = "Imports the Polyglot Python package for the Python API." - [[stays]] path = "compiler/sql_analysis/main/import_polyglot_sql.py" rule = 4 diff --git a/scripts/release_gate/_helpers/gate.py b/scripts/release_gate/_helpers/gate.py index e9187dcc56..019a2ac231 100644 --- a/scripts/release_gate/_helpers/gate.py +++ b/scripts/release_gate/_helpers/gate.py @@ -53,10 +53,15 @@ def verify_check_name(*, job: dict[str, Any], event: WorkflowEvent) -> str: "github": { "event_name": event.event_name, "repository": event.repository, - "event": {"pull_request": pull_request} + "event": { + "pull_request": pull_request, + "action": event.action, + "label": {"name": event.label}, + } if event.event_name == PULL_REQUEST_EVENT else {}, - } + }, + "needs": {"changes": {"outputs": {"partial": event.partial}}}, } return evaluate_template(template=str(job.get("name", VERIFY_JOB)), contexts=contexts).strip() diff --git a/scripts/release_gate/constants.py b/scripts/release_gate/constants.py index 90295d79b2..eb3902c36b 100644 --- a/scripts/release_gate/constants.py +++ b/scripts/release_gate/constants.py @@ -122,6 +122,35 @@ head_sha=_HEAD, expected_mergeable=True, ), + GateScenario( + description="feature pull request whose fast core passed without the full matrix it needs", + event=WorkflowEvent( + event_name=PULL_REQUEST_EVENT, + repository=_REPOSITORY, + head_ref="feature/inventory-sync", + head_repository=_REPOSITORY, + partial="true", + ), + verify_conclusion=CheckState.SUCCESS, + statuses=(), + head_sha=_HEAD, + expected_mergeable=False, + ), + GateScenario( + description="feature pull request whose only run followed an unrelated label", + event=WorkflowEvent( + event_name=PULL_REQUEST_EVENT, + repository=_REPOSITORY, + head_ref="feature/inventory-sync", + head_repository=_REPOSITORY, + action="labeled", + label="documentation", + ), + verify_conclusion=CheckState.SUCCESS, + statuses=(), + head_sha=_HEAD, + expected_mergeable=False, + ), GateScenario( description="merge group whose suites passed", event=WorkflowEvent(event_name="merge_group", repository=_REPOSITORY), diff --git a/scripts/release_gate/models.py b/scripts/release_gate/models.py index 3d66631dbb..935f34d2cc 100644 --- a/scripts/release_gate/models.py +++ b/scripts/release_gate/models.py @@ -15,6 +15,9 @@ class WorkflowEvent: repository: str head_ref: str | None = None head_repository: str | None = None + action: str | None = None + label: str | None = None + partial: str | None = None @dataclass(frozen=True) diff --git a/src/sqlbuild/.agents/skills/sqlbuild/references/docs/cli/server.md b/src/sqlbuild/.agents/skills/sqlbuild/references/docs/cli/server.md index aee81e65cd..05cafdfc59 100644 --- a/src/sqlbuild/.agents/skills/sqlbuild/references/docs/cli/server.md +++ b/src/sqlbuild/.agents/skills/sqlbuild/references/docs/cli/server.md @@ -11,6 +11,7 @@ Online: https://sqlbuild.com/docs/cli/server/ - Set retention - Grant roles to OIDC groups (beta) - Inspect published SQL +- Run ad-hoc commands - Define project roles Run the control API and scheduler without loading project Python: @@ -141,6 +142,76 @@ data. Every successful inspection records an audit entry without copying SQL int audit cannot be saved, the request fails without returning SQL. Revocation takes effect on the next request, including sessions created by [`sqb login`](login.md). +## Run ad-hoc commands + +People with `run.adhoc` can run a `sqb` command on the current publication without declaring a +job. Use the web UI's Run panel, or send the command as argument tokens after `sqb`: + +```json +{ + "location_id": "0191f0aa-0000-7000-8000-000000000001", + "target": "development", + "command": ["build", "--select", "+orders", "--full-refresh"], + "priority": 0 +} +``` + +`POST /api/control/runs/adhoc/check` with this body is a dry run. It returns the exact nodes the +command selects (`selection`), the nodes it writes (`write_set`), the options the worker receives +(`settings`), and each permission it needs with your decision (`permissions`). `error` explains a +command that can't run whatever your permissions, and `launchable` is `true` when the launch +would be accepted. A dry run queues nothing and records no audit entry. + +To launch, `POST /api/control/runs/adhoc` with the same fields plus a new `run_id` and a +`reason`. The response is `{"applied": true}`; sending the same `run_id` again returns +`{"applied": false}` and starts nothing. The server resolves selectors against the current +publication, and the worker runs exactly the resolved nodes. The target is always the one in the +request. + +| Verb | Ad-hoc runs | +| --- | --- | +| `build` | Dry run and launch | +| `test`, `seed`, `audit`, `freshness` | Dry run only; workers can't run them yet | +| Any other verb | Refused | + +Selectors use the CLI grammar: names, `seed:`, `source:`, `tag:`, `path:` and `test:`, with `+` +for upstream or downstream nodes. Positional selectors work as on the command line. + +| Flags | Verbs | Needs | +| --- | --- | --- | +| `--select`, `-s`, `--exclude` | All | `run.adhoc` on every selected node | +| `--full-refresh`, `--start-cursor`, `--end-cursor`, `--start-cursor-ts`, `--end-cursor-ts`, `--start-cursor-int`, `--end-cursor-int` | `build` | `run.rewrite_history` | +| `--allow-snapshot-full-refresh`, `--allow-snapshot-schema-change`, `--allow-table-type-downgrade`, `--allow-retention-decrease` | `build` | `run.rewrite_history` | +| `--warehouse` | All | `run.warehouse`, and the warehouse configured for the target on the location | +| `--max-microbatches`, `--no-load`, `--no-tests`, `--no-audits`, `--no-python`, `--fail-fast`, `--selection-diagnostics`, `--verbose` | `build` | `run.adhoc` | +| `--concurrency` | `build`, `test`, `seed`, `audit` | `run.adhoc` | +| `--test-timeout` | `build`, `test` | `run.adhoc` | +| `--case`, `--inspect` | `test` | `run.adhoc` | +| `--state`, `--fail-on-error`, `--fail-on-stale` | `freshness` | `run.adhoc` | + +A priority above 0 also needs `run.prioritise`. Among built-in roles only Admin grants +`run.rewrite_history`, `run.warehouse` and `run.prioritise`. Every other flag is refused with +its reason: + +| Refused flags | Reason | +| --- | --- | +| `--vars` | Variables change the published SQL | +| `--defer-to`, `--defer-sources-to`, `--defer-clone-from` | They read another target | +| `--load`, `--reload` | Source loads are not part of ad-hoc runs | +| `--allow-missing-migration-origin` | It can lose data | +| `--no-sql-analysis`, `--no-cache` | Workers verify the published SQL and manage their own cache | +| `--debug` | It prints executed SQL | +| `--since`, `--explain` | They need the worker's Git checkout | +| `--select-file` | It reads a file on the worker | +| `--target`, `--project-dir` and the `--dbt-*` paths | Workers run the published project for the request's target | +| `--json`, `--json-output`, `--event-output`, `--manifest`, `--no-color` | Workers own the run's output | + +For an ad-hoc run, the worker checks `--warehouse` against the warehouses the location +configures for the target before any statement runs. An unconfigured warehouse fails the run with +the reason `the requested warehouse is not configured for this target on this location; no +warehouse statement ran`, which the run page shows. A job's own `warehouse` setting is not +checked this way. + ## Define project roles Put project roles in `roles.toml` at the project root. `sqb location publish` resolves each @@ -164,7 +235,7 @@ permissions = ["view.evidence"] | Selectors | Same grammar as a job's `select`; an unknown tag or name fails publication | | No matching nodes | Publication succeeds with a warning; the grant gives nothing | | Permissions | Any non-administrative action; `user.manage`, `role.assign` and `audit.view` are rejected | -| Runs | `job.run`, `run.adhoc`, `run.rerun` and `run.rewrite_history` need the permission on every node the run writes | +| Runs | `job.run`, `run.rerun` and `run.rewrite_history` need the permission on every node the run writes; an [ad-hoc command](#run-ad-hoc-commands) needs `run.adhoc` and its flags' permissions on every node it selects; a priority above 0 also needs `run.prioritise` | | Triggers | `trigger.toggle` and `sensor.reset_cursor` need the permission on every node the trigger's job writes | | Listings | The catalog, statistics and run list need `view` on every node of the current publication; partial coverage is denied, not filtered | | Several roles | Grants add up across a person's roles | diff --git a/src/sqlbuild/.agents/skills/sqlbuild/references/docs/concepts/rules.md b/src/sqlbuild/.agents/skills/sqlbuild/references/docs/concepts/rules.md index 09d9a86da7..2f7792a7a3 100644 --- a/src/sqlbuild/.agents/skills/sqlbuild/references/docs/concepts/rules.md +++ b/src/sqlbuild/.agents/skills/sqlbuild/references/docs/concepts/rules.md @@ -12,6 +12,7 @@ Online: https://sqlbuild.com/docs/concepts/rules/ - How Rules work - SQL Rules - Built-in and custom Rules +- Dialect support - One definition of project validity - Rules compared with tests, audits, and contracts - Findings and intentional exceptions @@ -178,6 +179,26 @@ select = [ Define typed model or project checks over compiler-owned facts with `@rule`, `RuleContext`, and `Finding`. +## Dialect support + +SQL Rules, and the built-in Rules that read SQL, such as `SQBRMODEL102` and the SQL-test Rules +`SQBRTEST201` to `SQBRTEST203`, run only where SQLBuild parses the project's dialect. Built-in Rules +that read SQL support these adapters: + +| Adapter | Built-in Rules that read SQL | +|---|---| +| DuckDB, MotherDuck | Supported | +| PostgreSQL | Supported | +| Snowflake | Supported | +| SQLite | Supported | +| BigQuery, ClickHouse, Databricks, SQL Server | Not yet | + +Where SQLBuild does not parse the dialect yet, `sqb compile` and `sqb rules run` show `L007` +warnings that name it: one per file for SQL Rules, and one per model or SQL test listing the +built-in Rules that did not run. `L007` never changes an exit code, including for `sqb plan` and +`sqb build`: it reports a gap in SQLBuild's dialect support, not a problem in your SQL. A syntax +error in a supported dialect still fails the Rules that read it. + ## One definition of project validity `sqb compile` is authoritative. SQLBuild evaluates a project in this order: diff --git a/src/sqlbuild/_native.pyi b/src/sqlbuild/_native.pyi index 2ecb23ece5..d9dfe2d850 100644 --- a/src/sqlbuild/_native.pyi +++ b/src/sqlbuild/_native.pyi @@ -3458,6 +3458,7 @@ class PreparedCompileArtifacts: def format_sql_json(request_json: str) -> str: ... def query_fingerprint(sql: str, dialect: str) -> str: ... +def parse_warehouse_type(spelling: str, dialect: str) -> str | None: ... def lint_sql_json(request_json: str) -> str: ... def prepare_lint_sql( request: LintPreparationRequest, diff --git a/src/sqlbuild/adapter/type_system/constants.py b/src/sqlbuild/adapter/type_system/constants.py index 345fe35380..83af16becc 100644 --- a/src/sqlbuild/adapter/type_system/constants.py +++ b/src/sqlbuild/adapter/type_system/constants.py @@ -1,4 +1,4 @@ """Logging names shared by the Python and native type normalization.""" TYPE_NORMALIZATION_LOGGER_NAME: str = "sqlbuild.adapter" -TYPE_PARSE_FALLBACK_MESSAGE: str = "type normalization polyglot parse failed; falling back" +TYPE_PARSE_FALLBACK_MESSAGE: str = "type normalization parse failed; falling back" diff --git a/src/sqlbuild/adapter/type_system/main/_native_normalize_type.py b/src/sqlbuild/adapter/type_system/main/_native_normalize_type.py index 77f1c5d714..97882418d7 100644 --- a/src/sqlbuild/adapter/type_system/main/_native_normalize_type.py +++ b/src/sqlbuild/adapter/type_system/main/_native_normalize_type.py @@ -15,7 +15,7 @@ def normalize_native_type(*, type_sql: str, dialect: TypeDialect | str | None) -> NormalizedType: - """Return the native normalization of one type, raising as Polyglot does.""" + """Return the native normalization of one type, raising ValueError for an unknown dialect.""" (normalized_name, family, precision, scale, length), parse_error = _native.normalize_type( type_sql, str(dialect or "generic") diff --git a/src/sqlbuild/adapters/sqlserver/classes/sqlserver_adapter.py b/src/sqlbuild/adapters/sqlserver/classes/sqlserver_adapter.py index 2e50a2e916..36a8d42f3f 100644 --- a/src/sqlbuild/adapters/sqlserver/classes/sqlserver_adapter.py +++ b/src/sqlbuild/adapters/sqlserver/classes/sqlserver_adapter.py @@ -116,6 +116,7 @@ INFORMATION_SCHEMA_NULLABLE_VALUE, INTEGER_TYPE_TOKEN, ISO8601_CONVERT_STYLE, + TABLE_FUNCTION_RETURN_TYPE, USER_TABLE_OBJECT_TYPE, ) from sqlbuild.compiler.compile.types import FunctionLanguage @@ -319,9 +320,10 @@ def query(self, *, connection: Any, sql: str, limit: int | None) -> QueryResult: def render_create_schema(self, *, database: str | None, schema: str) -> tuple[str, ...]: del database escaped_schema: str = schema.replace("'", "''") + escaped_identifier: str = self.render_identifier(schema).replace("'", "''") return ( "IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = " - f"'{escaped_schema}') EXEC('CREATE SCHEMA {self.render_identifier(schema)}')", + f"'{escaped_schema}') EXEC('CREATE SCHEMA {escaped_identifier}')", ) def render_drop_schema(self, *, database: str | None, schema: str) -> tuple[str, ...]: @@ -831,11 +833,17 @@ def render_create_function( message="Python functions require an engine-specific implementation" ) arg_sql: str = ", ".join(f"@{arg.name} {arg.type}" for arg in arguments) - body_expression: str = body_sql.strip().removeprefix("SELECT").strip().rstrip(";") + body_query: str = body_sql.strip().rstrip(";") for arg in arguments: - body_expression = re.sub( - rf"\b{re.escape(str(arg.name))}\b", f"@{arg.name}", body_expression + body_query = re.sub(rf"\b{re.escape(str(arg.name))}\b", f"@{arg.name}", body_query) + if returns.upper() == TABLE_FUNCTION_RETURN_TYPE: + return ( + f"CREATE OR ALTER FUNCTION {destination}({arg_sql})\n" + "RETURNS TABLE\n" + "AS\n" + f"RETURN (\n{body_query}\n)", ) + body_expression: str = body_query.removeprefix("SELECT").strip() if returns.upper() == BOOLEAN_RETURN_TYPE and not body_expression.upper().startswith( ("CASE ", "CAST(") ): @@ -2399,7 +2407,7 @@ def render_merge( ) if update_assignments: merge_sql += f"WHEN MATCHED THEN UPDATE SET {update_assignments} " - merge_sql += f"WHEN NOT MATCHED THEN INSERT ({insert_columns}) VALUES ({insert_values})" + merge_sql += f"WHEN NOT MATCHED THEN INSERT ({insert_columns}) VALUES ({insert_values});" return (merge_sql,) def render_qualified_name( @@ -2449,7 +2457,15 @@ def render_source_expression_relation(self, *, expression: str) -> str: if stripped_expression.startswith("("): return stripped_expression lowered: str = stripped_expression.lower() - if lowered.startswith(("select", "with", "values")): + if re.match(r"with\b", lowered): + raise AdapterUserError( + message="SQL Server cannot read a source expression that starts with WITH: " + "a common table expression cannot sit inside a derived table", + code="A715", + help="rewrite the source expression as one SELECT, using subqueries instead of " + "WITH, or define it as a view or model and read that", + ) + if lowered.startswith(("select", "values")): return f"({stripped_expression})" return stripped_expression diff --git a/src/sqlbuild/adapters/sqlserver/constants.py b/src/sqlbuild/adapters/sqlserver/constants.py index 611c496afc..bb206e1ed9 100644 --- a/src/sqlbuild/adapters/sqlserver/constants.py +++ b/src/sqlbuild/adapters/sqlserver/constants.py @@ -1,6 +1,7 @@ """SQL Server adapter constants.""" BOOLEAN_RETURN_TYPE: str = "BIT" +TABLE_FUNCTION_RETURN_TYPE: str = "TABLE" DIFF_CHARACTER_LENGTH_TYPES: frozenset[str] = frozenset( {"binary", "char", "nchar", "nvarchar", "varbinary", "varchar"} ) diff --git a/src/sqlbuild/compiler/sql_analysis/main/import_polyglot.py b/src/sqlbuild/compiler/sql_analysis/main/import_polyglot.py deleted file mode 100644 index d38e3925d8..0000000000 --- a/src/sqlbuild/compiler/sql_analysis/main/import_polyglot.py +++ /dev/null @@ -1,13 +0,0 @@ -"""Public required Polyglot import entry.""" - -from typing import Any - -from sqlbuild.compiler.sql_analysis._helpers.polyglot import ( - import_polyglot as _import_polyglot, -) - - -def import_polyglot() -> Any: - """Return SQLBuild's required Polyglot SQL module.""" - - return _import_polyglot() diff --git a/src/sqlbuild/executor/scenario/_helpers/capture/columns.py b/src/sqlbuild/executor/scenario/_helpers/capture/columns.py index 6614e7cf63..add2b8fe3f 100644 --- a/src/sqlbuild/executor/scenario/_helpers/capture/columns.py +++ b/src/sqlbuild/executor/scenario/_helpers/capture/columns.py @@ -2,50 +2,63 @@ from __future__ import annotations +import json import logging +import re from dataclasses import dataclass from typing import Any +from sqlbuild._native import parse_warehouse_type from sqlbuild.adapter.contract.classes.base_adapter import BaseAdapter from sqlbuild.adapter.contract.models import ColumnInfo from sqlbuild.adapter.contract.types import TypeDialect -from sqlbuild.compiler.sql_analysis.main.import_polyglot import import_polyglot from sqlbuild.diagnostics.main.log_debug_event import log_debug_event from sqlbuild.errors.contracts.exceptions import ExecutorInputError from sqlbuild.executor.scenario.constants import ( BIGQUERY_BIGNUMERIC_TYPE, - BIGQUERY_BYTES_TYPE, BIGQUERY_TIMESTAMP_TYPE, BIGQUERY_VARCHAR_TYPES, BIGQUERY_WIDEN_TO_DOUBLE_TYPES, + DATABRICKS_ARRAY_TYPE, + DATABRICKS_INTERVAL_PREFIX, + DATABRICKS_MAP_ARGUMENT_COUNT, + DATABRICKS_MAP_TYPE, + DATABRICKS_NESTED_TYPES, + DATABRICKS_STRUCT_FIELD_PATTERN, + DATABRICKS_STRUCT_TYPE, DATABRICKS_TIMESTAMP_NTZ_TYPE, DATABRICKS_TIMESTAMP_TYPE, + DATABRICKS_VARIANT_TYPE, DATABRICKS_VOID_TYPE, DUCKDB_BLOB_TYPE, DUCKDB_HUGEINT_TYPE, DUCKDB_INTEGER_TYPE, DUCKDB_LIST_TYPE, + DUCKDB_SPATIAL_TYPES, DUCKDB_UHUGEINT_TYPE, FALLBACK_BINARY_TYPES, FALLBACK_BOOLEAN_TYPES, FALLBACK_DATE_TYPE, FALLBACK_DECIMAL_TYPES, + FALLBACK_DOUBLE_TYPES, FALLBACK_FLOAT_TYPES, FALLBACK_JSON_TYPES, - FALLBACK_POLYGLOT_BASE_TYPES, - FALLBACK_SIGNED_INTEGER_TYPES, + FALLBACK_SIGNED_INTEGER_LOCAL_TYPES, FALLBACK_STRUCTURED_TYPES, FALLBACK_TIME_TYPES, FALLBACK_TIMESTAMP_TOKEN, FALLBACK_TIMESTAMP_TYPES, FALLBACK_UNSIGNED_INTEGER_TYPES, FALLBACK_VARCHAR_TYPES, + GENERIC_LOCAL_BASE_TYPES, GENERIC_WIDEN_TO_DOUBLE_TYPES, LOCAL_TYPE_BIGINT, LOCAL_TYPE_BOOLEAN, LOCAL_TYPE_DATE, LOCAL_TYPE_DECIMAL, + LOCAL_TYPE_DIALECTS, LOCAL_TYPE_DOUBLE, + LOCAL_TYPE_FLOAT, LOCAL_TYPE_INT, LOCAL_TYPE_INT128, LOCAL_TYPE_JSON, @@ -55,16 +68,10 @@ LOCAL_TYPE_TIME, LOCAL_TYPE_TIMESTAMP, LOCAL_TYPE_TIMESTAMPTZ, + LOCAL_TYPE_TIMETZ, LOCAL_TYPE_UINT128, LOCAL_TYPE_VARCHAR, - MALFORMED_RENDERED_TYPE_TOKEN, - POLYGLOT_ARRAY_TYPE, - POLYGLOT_COLLECTION_TYPES, - POLYGLOT_CUSTOM_TYPE, - POLYGLOT_DECIMAL_TYPES, - POLYGLOT_OBJECT_TYPE, - POLYGLOT_SPATIAL_AND_MONEY_TYPES, - POLYGLOT_VARCHAR_TYPES, + LOCAL_TYPE_VARIANT, POSTGRES_BIGSERIAL_TYPE, POSTGRES_PARAMETERIZED_DECIMAL_TYPES, POSTGRES_PARAMETERIZED_TEXT_TYPES, @@ -79,6 +86,8 @@ SNOWFLAKE_TIMEZONE_TIMESTAMP_TYPES, SNOWFLAKE_VECTOR_TYPE, SNOWFLAKE_WIDEN_TO_DOUBLE_TYPES, + STRUCT_FIELD_NAME_PATTERN, + TSQL_LOCAL_TYPES, TYPE_ARGUMENT_CLOSE_ANGLE, TYPE_ARGUMENT_CLOSE_PAREN, TYPE_ARGUMENT_CLOSE_TOKENS, @@ -86,7 +95,16 @@ TYPE_ARGUMENT_OPEN_TOKENS, TYPE_ARGUMENT_SEPARATOR, TYPE_PATTERN_WILDCARD, - UNTYPED_ARRAY_TYPE, + TYPE_TREE_COLLECTIONS, + TYPE_TREE_DECIMAL, + TYPE_TREE_FLOAT, + TYPE_TREE_KIND, + TYPE_TREE_LOCAL_TYPES, + TYPE_TREE_MAP, + TYPE_TREE_STRUCT, + TYPE_TREE_TEXT_KINDS, + TYPE_TREE_TIME, + TYPE_TREE_TIMESTAMP, ) from sqlbuild.executor.scenario.models import ScenarioSnapshotColumn @@ -194,12 +212,12 @@ def local_type_for_warehouse_type( if dialect_type is not None: return dialect_type - polyglot_local_type: str | None = _local_type_with_polyglot( + parsed_local_type: str | None = _local_type_with_sqlquack( warehouse_type=warehouse_type, sql_analysis_dialect=sql_analysis_dialect, ) - if polyglot_local_type is not None: - return polyglot_local_type + if parsed_local_type is not None: + return parsed_local_type return _fallback_local_type_for_warehouse_type(warehouse_type) @@ -277,10 +295,12 @@ def _dialect_pre_local_type(*, warehouse_type: str, sql_analysis_dialect: str | if dialect == TypeDialect.BIGQUERY: return _bigquery_pre_local_type(base=base, args=args) if dialect == TypeDialect.DATABRICKS: - return _databricks_pre_local_type(base=base, args=args) - if dialect == TypeDialect.DUCKDB: + return _databricks_local_type(warehouse_type) + if dialect in LOCAL_TYPE_DIALECTS: return _duckdb_pre_local_type(base=base, args=args) - if dialect is None and base not in FALLBACK_POLYGLOT_BASE_TYPES: + if dialect == TypeDialect.TSQL: + return TSQL_LOCAL_TYPES.get(base) + if dialect is None and base not in GENERIC_LOCAL_BASE_TYPES: return LOCAL_TYPE_VARCHAR return None @@ -317,22 +337,72 @@ def _bigquery_pre_local_type(*, base: str, args: tuple[str, ...]) -> str | None: return None -def _databricks_pre_local_type(*, base: str, args: tuple[str, ...]) -> str | None: +def _databricks_local_type(type_text: str) -> str | None: + """Map a Databricks type, nesting included; None leaves a scalar to the fallback.""" + + pattern: _TypePattern = _split_type_text(type_text.strip()) + base: str = pattern.base.upper() + if base in DATABRICKS_NESTED_TYPES: + return _databricks_nested_local_type(type_text=type_text.strip(), pattern=pattern) if base == DATABRICKS_TIMESTAMP_TYPE: return LOCAL_TYPE_TIMESTAMPTZ if base == DATABRICKS_TIMESTAMP_NTZ_TYPE: return LOCAL_TYPE_TIMESTAMP if base == DATABRICKS_VOID_TYPE: return LOCAL_TYPE_VARCHAR + if base.startswith(DATABRICKS_INTERVAL_PREFIX): + return " ".join(base.split()) + if base == DATABRICKS_VARIANT_TYPE: + return LOCAL_TYPE_VARIANT return None +def _databricks_nested_local_type(*, type_text: str, pattern: _TypePattern) -> str: + base: str = pattern.base.upper() + args: tuple[str, ...] = pattern.args + if base == DATABRICKS_ARRAY_TYPE and len(args) == 1: + return _databricks_element_local_type(element=args[0], type_text=type_text) + "[]" + if base == DATABRICKS_MAP_TYPE and len(args) == DATABRICKS_MAP_ARGUMENT_COUNT: + key: str = _databricks_element_local_type(element=args[0], type_text=type_text) + value: str = _databricks_element_local_type(element=args[1], type_text=type_text) + return f"MAP({key}, {value})" + if base == DATABRICKS_STRUCT_TYPE and args: + fields: list[str] = [] + for field in args: + match: re.Match[str] | None = DATABRICKS_STRUCT_FIELD_PATTERN.fullmatch(field) + if match is None: + raise _unmapped_databricks_type(element=field, type_text=type_text) + name: str = match.group("name").strip("`") + field_type: str = _databricks_element_local_type( + element=match.group("type"), type_text=type_text + ) + fields.append(f"{_struct_field_name(name)} {field_type}") + return f"STRUCT({', '.join(fields)})" + raise _unmapped_databricks_type(element=type_text, type_text=type_text) + + +def _databricks_element_local_type(*, element: str, type_text: str) -> str: + local_type: str | None = _databricks_local_type(element) or _known_local_type(element) + if local_type is None: + raise _unmapped_databricks_type(element=element, type_text=type_text) + return local_type + + +def _unmapped_databricks_type(*, element: str, type_text: str) -> ExecutorInputError: + return ExecutorInputError( + f"Databricks type '{type_text}' has no local DuckDB type: SQLBuild cannot map " + f"'{element.strip()}' inside it", + code=SCENARIO_LOCAL_TYPE_INVALID, + help="Add a scenario local type override for this warehouse type.", + ) + + def _duckdb_pre_local_type(*, base: str, args: tuple[str, ...]) -> str | None: if base == DUCKDB_HUGEINT_TYPE: return LOCAL_TYPE_INT128 if base == DUCKDB_UHUGEINT_TYPE: return LOCAL_TYPE_UINT128 - if base == DUCKDB_BLOB_TYPE: + if base == DUCKDB_BLOB_TYPE or base in DUCKDB_SPATIAL_TYPES: return LOCAL_TYPE_VARCHAR if base == DUCKDB_LIST_TYPE and args: inner_type: str = _fallback_local_type_for_warehouse_type(args[0]) @@ -342,68 +412,37 @@ def _duckdb_pre_local_type(*, base: str, args: tuple[str, ...]) -> str | None: return None -def _local_type_with_polyglot( +def _local_type_with_sqlquack( *, warehouse_type: str, sql_analysis_dialect: str | None ) -> str | None: - polyglot_module: Any = import_polyglot() + """Map a type through SQLQuack's parser for its dialect; DuckDB spellings are already local.""" + dialect: TypeDialect | None = _coerce_type_dialect(sql_analysis_dialect) + if dialect in LOCAL_TYPE_DIALECTS: + return warehouse_type.strip() try: - data_type: Any = polyglot_module.parse_data_type( - warehouse_type, - dialect=sql_analysis_dialect or "generic", - ) - except polyglot_module.PolyglotError as error: + tree_json: str | None = parse_warehouse_type(warehouse_type, sql_analysis_dialect or "") + except ValueError as error: log_debug_event( logger=_DEBUG_LOGGER, - message="scenario snapshot type polyglot conversion failed; falling back", + message="scenario snapshot type parse failed; falling back", warehouse_type=warehouse_type, sql_analysis_dialect=sql_analysis_dialect, sqlbuild_error=str(error), ) return None - - type_name: str = _polyglot_type_name(data_type) + if tree_json is None: + return None base_type: str = _parse_type_pattern(warehouse_type).base - if type_name in POLYGLOT_DECIMAL_TYPES: - rendered_bigdecimal_type: str = data_type.sql(dialect="duckdb").strip() - if MALFORMED_RENDERED_TYPE_TOKEN not in rendered_bigdecimal_type: - return rendered_bigdecimal_type - return _decimal_local_type_from_polyglot(data_type) or LOCAL_TYPE_VARCHAR - if type_name in POLYGLOT_VARCHAR_TYPES: - return LOCAL_TYPE_VARCHAR - if type_name == POLYGLOT_OBJECT_TYPE: - return LOCAL_TYPE_JSON - if type_name == POLYGLOT_ARRAY_TYPE and not _polyglot_element_type(data_type): - return LOCAL_TYPE_JSON - if type_name in POLYGLOT_COLLECTION_TYPES and _polyglot_element_type(data_type): - return data_type.sql(dialect="duckdb").strip() - if type_name in POLYGLOT_SPATIAL_AND_MONEY_TYPES: - return LOCAL_TYPE_VARCHAR - dialect: TypeDialect | None = _coerce_type_dialect(sql_analysis_dialect) if dialect == TypeDialect.POSTGRES and base_type in POSTGRES_VARCHAR_TYPES: return LOCAL_TYPE_VARCHAR if dialect == TypeDialect.POSTGRES and base_type in POSTGRES_SERIAL_TYPES: return _postgres_serial_local_type(base_type) - if ( - dialect == TypeDialect.BIGQUERY - and base_type == BIGQUERY_BYTES_TYPE - and data_type.expressions - ): - return LOCAL_TYPE_VARCHAR if dialect == TypeDialect.SNOWFLAKE and base_type == SNOWFLAKE_VECTOR_TYPE: return LOCAL_TYPE_JSON - - local_type: str = data_type.sql(dialect="duckdb").strip() - if ( - not local_type - or local_type == UNTYPED_ARRAY_TYPE - or MALFORMED_RENDERED_TYPE_TOKEN in local_type - ): + local_type: str | None = _local_type_from_tree(json.loads(tree_json)) + if local_type is None: return _fallback_local_type_for_warehouse_type(warehouse_type) - if local_type.startswith("BLOB"): - return "VARCHAR" - if local_type.startswith("TIMESTAMPTZ("): - return "TIMESTAMPTZ" if local_type == LOCAL_TYPE_REAL and _should_widen_real_to_double( warehouse_type=warehouse_type, sql_analysis_dialect=sql_analysis_dialect, @@ -412,18 +451,70 @@ def _local_type_with_polyglot( return local_type +def _local_type_from_tree(tree: dict[str, Any]) -> str | None: + """The DuckDB type of a SQLQuack type tree, or None when it has no fixed local type.""" + + kind: str = str(tree.get(TYPE_TREE_KIND, "")) + if kind in TYPE_TREE_LOCAL_TYPES: + return TYPE_TREE_LOCAL_TYPES[kind] + if kind in TYPE_TREE_TEXT_KINDS: + length: Any = tree.get("length") + return LOCAL_TYPE_TEXT if length is None else f"{LOCAL_TYPE_TEXT}({length})" + if kind == TYPE_TREE_FLOAT: + return LOCAL_TYPE_REAL if tree.get("real_spelling") else LOCAL_TYPE_FLOAT + if kind == TYPE_TREE_DECIMAL: + arguments: list[str] = [ + str(value) for value in (tree.get("precision"), tree.get("scale")) if value is not None + ] + return LOCAL_TYPE_DECIMAL + (f"({', '.join(arguments)})" if arguments else "") + if kind == TYPE_TREE_TIME: + return LOCAL_TYPE_TIMETZ if tree.get("timezone") else LOCAL_TYPE_TIME + if kind == TYPE_TREE_TIMESTAMP: + return LOCAL_TYPE_TIMESTAMPTZ if tree.get("timezone") else LOCAL_TYPE_TIMESTAMP + if kind in TYPE_TREE_COLLECTIONS: + element: str | None = _local_type_from_tree(tree.get("element_type") or {}) + return None if element is None else f"{element}[]" + if kind == TYPE_TREE_STRUCT: + fields: list[str] = [] + for field in tree.get("fields") or []: + field_type: str | None = _local_type_from_tree(field.get("data_type") or {}) + if field_type is None: + return None + fields.append(f"{_struct_field_name(str(field.get('name', '')))} {field_type}") + return f"STRUCT({', '.join(fields)})" if fields else None + if kind == TYPE_TREE_MAP: + key: str | None = _local_type_from_tree(tree.get("key_type") or {}) + value: str | None = _local_type_from_tree(tree.get("value_type") or {}) + return None if key is None or value is None else f"MAP({key}, {value})" + return None + + +def _struct_field_name(name: str) -> str: + if STRUCT_FIELD_NAME_PATTERN.fullmatch(name): + return name + return '"' + name.replace('"', '""') + '"' + + def _fallback_local_type_for_warehouse_type(warehouse_type: str) -> str: """Return a conservative local type when SQL analysis cannot classify a type.""" + return _known_local_type(warehouse_type) or LOCAL_TYPE_VARCHAR + + +def _known_local_type(warehouse_type: str) -> str | None: + """The local type of a type name SQLBuild knows by its base name alone, else None.""" + normalized_type: str = warehouse_type.strip().upper() - base_type: str = normalized_type.split("(", 1)[0].strip() + base_type: str = _parse_type_pattern(normalized_type).base if base_type in FALLBACK_BOOLEAN_TYPES: return LOCAL_TYPE_BOOLEAN - if base_type in FALLBACK_SIGNED_INTEGER_TYPES: - return LOCAL_TYPE_BIGINT + if base_type in FALLBACK_SIGNED_INTEGER_LOCAL_TYPES: + return FALLBACK_SIGNED_INTEGER_LOCAL_TYPES[base_type] if base_type in FALLBACK_UNSIGNED_INTEGER_TYPES: return LOCAL_TYPE_BIGINT if base_type in FALLBACK_FLOAT_TYPES: + return LOCAL_TYPE_FLOAT + if base_type in FALLBACK_DOUBLE_TYPES: return LOCAL_TYPE_DOUBLE if base_type in FALLBACK_DECIMAL_TYPES: return _decimal_local_type(normalized_type) @@ -441,7 +532,7 @@ def _fallback_local_type_for_warehouse_type(warehouse_type: str) -> str: return LOCAL_TYPE_VARCHAR if base_type in FALLBACK_STRUCTURED_TYPES: return LOCAL_TYPE_JSON - return LOCAL_TYPE_VARCHAR + return None def _postgres_serial_local_type(base_type: str) -> str: @@ -453,7 +544,10 @@ def _postgres_serial_local_type(base_type: str) -> str: def _parse_type_pattern(type_text: str) -> _TypePattern: - normalized_type: str = type_text.strip().upper() + return _split_type_text(type_text.strip().upper()) + + +def _split_type_text(normalized_type: str) -> _TypePattern: if not normalized_type: return _TypePattern(base="") open_index: int = _first_type_arg_open_index(normalized_type) @@ -538,37 +632,6 @@ def _decimal_local_type(normalized_type: str) -> str: return f"DECIMAL({precision_scale})" -def _decimal_local_type_from_polyglot(data_type: Any) -> str | None: - precision_scale: str | None = _polyglot_precision_scale(data_type) - if precision_scale is None: - return LOCAL_TYPE_DECIMAL - return f"DECIMAL({precision_scale})" - - -def _polyglot_precision_scale(data_type: Any) -> str | None: - args: dict[str, Any] = dict(getattr(data_type, "args", {}) or {}) - values: list[str] = [ - str(value) for value in (args.get("precision"), args.get("scale")) if value is not None - ] - if not values: - return None - return ", ".join(values) - - -def _polyglot_type_name(data_type: Any) -> str: - args: dict[str, Any] = dict(getattr(data_type, "args", {}) or {}) - data_type_name: str = str(args.get("data_type", "")).upper() - if data_type_name == POLYGLOT_CUSTOM_TYPE: - return str(args.get("name", "")).upper().replace(" ", "") - return data_type_name - - -def _polyglot_element_type(data_type: Any) -> dict[str, Any] | None: - args: dict[str, Any] = dict(getattr(data_type, "args", {}) or {}) - element_type: Any = args.get("element_type") - return element_type if isinstance(element_type, dict) else None - - def _should_widen_real_to_double(*, warehouse_type: str, sql_analysis_dialect: str | None) -> bool: normalized_type: str = warehouse_type.strip().upper() base_type: str = normalized_type.split("(", 1)[0].strip() diff --git a/src/sqlbuild/executor/scenario/constants.py b/src/sqlbuild/executor/scenario/constants.py index 1f87454379..416287896c 100644 --- a/src/sqlbuild/executor/scenario/constants.py +++ b/src/sqlbuild/executor/scenario/constants.py @@ -1,5 +1,9 @@ """Scenario execution and local replay constants.""" +import re + +from sqlbuild.adapter.contract.types import TypeDialect + SCENARIO_EXEC_FIXTURE_FAILED: str = "X501" SCENARIO_EXEC_SEED_FAILED: str = "X502" SCENARIO_EXEC_MODEL_FAILED: str = "X503" @@ -35,6 +39,9 @@ LOCAL_TYPE_REAL: str = "REAL" LOCAL_TYPE_SMALLINT: str = "SMALLINT" LOCAL_TYPE_TEXT: str = "TEXT" +LOCAL_TYPE_FLOAT: str = "FLOAT" +LOCAL_TYPE_TIMETZ: str = "TIMETZ" +LOCAL_TYPE_VARIANT: str = "VARIANT" LOCAL_TYPE_TIME: str = "TIME" LOCAL_TYPE_TIMESTAMP: str = "TIMESTAMP" LOCAL_TYPE_TIMESTAMPTZ: str = "TIMESTAMPTZ" @@ -82,19 +89,29 @@ SNOWFLAKE_VECTOR_TYPE: str = "VECTOR" BIGQUERY_BIGNUMERIC_TYPE: str = "BIGNUMERIC" BIGQUERY_VARCHAR_TYPES: frozenset[str] = frozenset({"RANGE", "BYTES"}) -BIGQUERY_BYTES_TYPE: str = "BYTES" BIGQUERY_TIMESTAMP_TYPE: str = "TIMESTAMP" BIGQUERY_WIDEN_TO_DOUBLE_TYPES: frozenset[str] = frozenset({"FLOAT64"}) DATABRICKS_TIMESTAMP_TYPE: str = "TIMESTAMP" DATABRICKS_TIMESTAMP_NTZ_TYPE: str = "TIMESTAMP_NTZ" DATABRICKS_VOID_TYPE: str = "VOID" +DATABRICKS_INTERVAL_PREFIX: str = "INTERVAL" +DATABRICKS_VARIANT_TYPE: str = "VARIANT" +DATABRICKS_ARRAY_TYPE: str = "ARRAY" +DATABRICKS_MAP_TYPE: str = "MAP" +DATABRICKS_STRUCT_TYPE: str = "STRUCT" +DATABRICKS_NESTED_TYPES: frozenset[str] = frozenset({"ARRAY", "MAP", "STRUCT"}) +DATABRICKS_MAP_ARGUMENT_COUNT: int = 2 +DATABRICKS_STRUCT_FIELD_PATTERN: re.Pattern[str] = re.compile( + r"\s*(?P`[^`]+`|[A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*|\s+)(?P\S.*?)\s*" +) DUCKDB_HUGEINT_TYPE: str = "HUGEINT" DUCKDB_UHUGEINT_TYPE: str = "UHUGEINT" DUCKDB_BLOB_TYPE: str = "BLOB" +DUCKDB_SPATIAL_TYPES: frozenset[str] = frozenset({"GEOMETRY", "GEOGRAPHY"}) DUCKDB_LIST_TYPE: str = "LIST" DUCKDB_INTEGER_TYPE: str = "INTEGER" -FALLBACK_POLYGLOT_BASE_TYPES: frozenset[str] = frozenset( +GENERIC_LOCAL_BASE_TYPES: frozenset[str] = frozenset( { "BOOL", "BOOLEAN", @@ -118,24 +135,72 @@ "JSON", } ) -POLYGLOT_DECIMAL_TYPES: frozenset[str] = frozenset({"BIGDECIMAL", "DECIMAL"}) -POLYGLOT_VARCHAR_TYPES: frozenset[str] = frozenset({"NULL", "RANGE", "XML"}) -POLYGLOT_OBJECT_TYPE: str = "OBJECT" -POLYGLOT_ARRAY_TYPE: str = "ARRAY" -POLYGLOT_COLLECTION_TYPES: frozenset[str] = frozenset({"ARRAY", "LIST"}) -POLYGLOT_SPATIAL_AND_MONEY_TYPES: frozenset[str] = frozenset({"GEOGRAPHY", "GEOMETRY", "MONEY"}) -POLYGLOT_CUSTOM_TYPE: str = "CUSTOM" +TYPE_TREE_KIND: str = "data_type" +TYPE_TREE_LOCAL_TYPES: dict[str, str] = { + "boolean": "BOOLEAN", + "tiny_int": "TINYINT", + "small_int": "SMALLINT", + "int": "INT", + "big_int": "BIGINT", + "int128": "HUGEINT", + "uint8": "UTINYINT", + "uint16": "USMALLINT", + "uint32": "UINTEGER", + "uint64": "UBIGINT", + "uint128": "UHUGEINT", + "double": "DOUBLE", + "date": "DATE", + "interval": "INTERVAL", + "json": "JSON", + "json_b": "JSON", + "uuid": "UUID", + "binary": "VARCHAR", + "var_binary": "VARCHAR", + "blob": "VARCHAR", + "geography": "VARCHAR", + "geometry": "VARCHAR", + "object": "JSON", +} +TYPE_TREE_TEXT_KINDS: frozenset[str] = frozenset({"char", "var_char", "string", "text"}) +TYPE_TREE_FLOAT: str = "float" +TYPE_TREE_DECIMAL: str = "decimal" +TYPE_TREE_TIME: str = "time" +TYPE_TREE_TIMESTAMP: str = "timestamp" +TYPE_TREE_COLLECTIONS: frozenset[str] = frozenset({"array", "list"}) +TYPE_TREE_STRUCT: str = "struct" +TYPE_TREE_MAP: str = "map" +STRUCT_FIELD_NAME_PATTERN: re.Pattern[str] = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") +LOCAL_TYPE_DIALECTS: frozenset[TypeDialect] = frozenset( + {TypeDialect.DUCKDB, TypeDialect.MOTHERDUCK} +) FALLBACK_BOOLEAN_TYPES: frozenset[str] = frozenset({"BOOL", "BOOLEAN"}) -FALLBACK_SIGNED_INTEGER_TYPES: frozenset[str] = frozenset( - {"TINYINT", "SMALLINT", "INT", "INTEGER", "BIGINT", "HUGEINT"} -) +FALLBACK_SIGNED_INTEGER_LOCAL_TYPES: dict[str, str] = { + "TINYINT": "TINYINT", + "SMALLINT": "SMALLINT", + "INT": "INT", + "INTEGER": "INT", + "BIGINT": "BIGINT", + "HUGEINT": "HUGEINT", +} FALLBACK_UNSIGNED_INTEGER_TYPES: frozenset[str] = frozenset( {"UTINYINT", "USMALLINT", "UINTEGER", "UBIGINT", "UHUGEINT"} ) -FALLBACK_FLOAT_TYPES: frozenset[str] = frozenset( - {"FLOAT", "FLOAT4", "REAL", "DOUBLE", "DOUBLE PRECISION", "FLOAT8"} +FALLBACK_FLOAT_TYPES: frozenset[str] = frozenset({"FLOAT", "FLOAT4", "REAL"}) +FALLBACK_DOUBLE_TYPES: frozenset[str] = frozenset( + {"DOUBLE", "DOUBLE PRECISION", "FLOAT8", "FLOAT64"} ) +TSQL_LOCAL_TYPES: dict[str, str] = { + "BIT": "BOOLEAN", + "DATETIME2": "TIMESTAMP", + "SMALLDATETIME": "TIMESTAMP", + "DATETIMEOFFSET": "TIMESTAMPTZ", + "UNIQUEIDENTIFIER": "UUID", + "FLOAT": "DOUBLE", + "REAL": "FLOAT", + "MONEY": "DECIMAL(19,4)", + "SMALLMONEY": "DECIMAL(10,4)", +} FALLBACK_DECIMAL_TYPES: frozenset[str] = frozenset({"DECIMAL", "DEC", "NUMERIC", "NUMBER"}) FALLBACK_DATE_TYPE: str = "DATE" FALLBACK_TIME_TYPES: frozenset[str] = frozenset({"TIME", "TIME WITH TIME ZONE", "TIMETZ"}) @@ -147,7 +212,7 @@ FALLBACK_JSON_TYPES: frozenset[str] = frozenset({"JSON", "JSONB"}) FALLBACK_BINARY_TYPES: frozenset[str] = frozenset({"BLOB", "BYTEA", "BINARY", "VARBINARY"}) FALLBACK_STRUCTURED_TYPES: frozenset[str] = frozenset( - {"ARRAY", "OBJECT", "VARIANT", "JSON", "JSONB"} + {"ARRAY", "OBJECT", "VARIANT", "JSON", "JSONB", "MAP", "STRUCT"} ) SNOWFLAKE_WIDEN_TO_DOUBLE_TYPES: frozenset[str] = SNOWFLAKE_DOUBLE_TYPES GENERIC_WIDEN_TO_DOUBLE_TYPES: frozenset[str] = frozenset({"DOUBLE", "DOUBLE PRECISION", "FLOAT8"}) @@ -159,8 +224,6 @@ TYPE_ARGUMENT_CLOSE_TOKENS: frozenset[str] = frozenset({")", ">"}) TYPE_ARGUMENT_SEPARATOR: str = "," TYPE_PATTERN_WILDCARD: str = "*" -MALFORMED_RENDERED_TYPE_TOKEN: str = ")(" -UNTYPED_ARRAY_TYPE: str = "[]" JSONL_BLANK_LINE: str = "" LOCAL_SCENARIO_DIALECT: str = "duckdb" diff --git a/tests/e2e/src/sqlbuild/cli/commands/main/rules/_test_types.py b/tests/e2e/src/sqlbuild/cli/commands/main/rules/_test_types.py index f4859f7693..a5443748b1 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/rules/_test_types.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/rules/_test_types.py @@ -179,6 +179,22 @@ class RankingSortDefaultCase: expected_codes: tuple[str, ...] +@dataclass(frozen=True) +class UnparsedDialectRulesCase: + description: str + adapter_toml: str + expected_codes: tuple[str, ...] + expected_message: str + expected_exit: int + + +@dataclass(frozen=True) +class ReplayedFindingExitCase: + description: str + code: str + expected_exit: int + + @dataclass(frozen=True) class FixableReportingCase: description: str @@ -384,3 +400,5 @@ class SyntaxTreeRuleE2ETestCase: description: str model_sql: str expected_messages: tuple[str, ...] + adapter: str = "duckdb" + other_models: dict[str, str] = field(default_factory=dict) diff --git a/tests/e2e/src/sqlbuild/cli/commands/main/rules/helpers.py b/tests/e2e/src/sqlbuild/cli/commands/main/rules/helpers.py index 42c1bcbcbc..7dc6388739 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/rules/helpers.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/rules/helpers.py @@ -73,11 +73,13 @@ def write_custom_rule_project( selected_rules: tuple[str, ...], files: tuple[tuple[str, str], ...], configuration: str = "", + adapter: str = "duckdb", ) -> None: - """Write a DuckDB project whose selected custom Rules live in the given files.""" + """Write a project, DuckDB by default, whose selected custom Rules live in the given files.""" (project_dir / "sqlbuild_project.toml").write_text( - f'name = "orders"\nadapter = "duckdb"\n\n[rules]\nselect = {json.dumps(selected_rules)}\n' + f'name = "orders"\nadapter = "{adapter}"\n\n' + f"[rules]\nselect = {json.dumps(selected_rules)}\n" "\n[rules.thresholds]\nmin_custom_rule_test_cases = 0\n" + configuration, encoding="utf-8", ) diff --git a/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_clickhouse_comments.py b/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_clickhouse_comments.py index 0cc24a9f8b..ba1ee05735 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_clickhouse_comments.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_clickhouse_comments.py @@ -23,14 +23,14 @@ "test_case", [ ClickHouseCommentE2ETestCase( - description="a hash comment with an apostrophe is skipped", + description="a hash comment with an apostrophe is skipped, reported unavailable until SQLBuild parses the dialect", comment="# don't keep the star", - expected_location="models/orders.sql:4:8 [SQBRMODEL102]", + expected_location="models/orders.sql:1:1 [L007]", ), ClickHouseCommentE2ETestCase( - description="a double-slash comment with an apostrophe is skipped", + description="a double-slash comment with an apostrophe is skipped, reported unavailable until SQLBuild parses the dialect", comment="// don't keep the star", - expected_location="models/orders.sql:4:8 [SQBRMODEL102]", + expected_location="models/orders.sql:1:1 [L007]", ), ], ids=lambda case: case.description, diff --git a/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_rule_positions.py b/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_rule_positions.py index 62f0510870..788b5698e2 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_rule_positions.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_rule_positions.py @@ -140,7 +140,24 @@ def test_given_model_query_layout_when_running_rules_then_findings_use_file_posi "Star/3:o.*,", "Subquery/3:(SEL", ), - ) + ), + SyntaxTreeRuleE2ETestCase( + description="Snowflake reads relation markers as tables at their source positions", + adapter="snowflake", + model_sql=( + 'MODEL (description "Orders", database analytics, schema analytics);\n' + 'WITH c AS (SELECT * FROM __ref("customers"))\n' + 'SELECT o.*, c.name FROM __ref("raw_orders") o JOIN c ON o.id = c.id\n' + ), + other_models={ + name: ( + f'MODEL (description "{name}", database analytics, schema analytics);\n' + "SELECT 1 AS id, 'a' AS name\n" + ) + for name in ("customers", "raw_orders") + }, + expected_messages=("CTE/2:c AS", "Join/2:JOIN", "Star/2:SELE", "Star/2:o.*,"), + ), ], ids=lambda case: case.description, ) @@ -150,7 +167,11 @@ def test_given_custom_rule_over_the_syntax_tree_when_compiling_then_nodes_point_ write_custom_rule_project( project_dir=tmp_path, selected_rules=("XSQBRTREE001",), - files=(("rules/tree.py", _TREE_RULE),), + files=( + ("rules/tree.py", _TREE_RULE), + *((f"models/{name}.sql", sql) for name, sql in test_case.other_models.items()), + ), + adapter=test_case.adapter, ) (tmp_path / "models" / "orders.sql").write_text(test_case.model_sql, encoding="utf-8") diff --git a/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_sql_quality_rules.py b/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_sql_quality_rules.py index 09961d554d..9199ee3e4b 100644 --- a/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_sql_quality_rules.py +++ b/tests/e2e/src/sqlbuild/cli/commands/main/rules/test_sql_quality_rules.py @@ -9,13 +9,16 @@ import pytest +from sqlbuild._native import rules_bulk_cache_path from tests.e2e.src.sqlbuild.cli.commands.main.rules._test_types import ( ConstantLiteralCase, DuplicateLiteralCase, FixableReportingCase, RankingKeyProofCase, RankingSortDefaultCase, + ReplayedFindingExitCase, SqlQualityRuleCase, + UnparsedDialectRulesCase, ) from tests.e2e.src.sqlbuild.cli.commands.main.rules.helpers import ( diagnostic_codes, @@ -557,5 +560,125 @@ def test_given_repeated_long_literal_when_running_rules_then_help_names_the_shar ), result.stdout +@pytest.mark.parametrize( + "test_case", + ( + ReplayedFindingExitCase( + description="a SQL rule finding fails every command", + code="SQLCLEAN043", + expected_exit=1, + ), + ReplayedFindingExitCase( + description="a lint-unavailable warning fails no command", + code="L007", + expected_exit=0, + ), + ), + ids=lambda case: case.description, +) +def test_given_cached_sql_finding_when_running_commands_then_only_rule_findings_fail( + test_case: ReplayedFindingExitCase, tmp_path: Path +) -> None: + write_quality_project( + project_dir=tmp_path, + toml=( + 'name = "orders"\nadapter = "duckdb"\ndefault_target = "dev"\n\n' + '[connection]\ndatabase = "plan.duckdb"\n\n[targets.dev]\nschema = "analytics"\n\n' + '[rules]\nselect = ["SQLCLEAN043"]\n' + ), + upstream=_RAW_HEADER + _UPSTREAM_SQL, + summary=( + _SUMMARY_HEADER + "SELECT o.order_id, ROW_NUMBER() OVER (ORDER BY o.amount," + " o.status, o.note, o.customer_id, o.amount + 1, o.order_id) AS rn\n" + 'FROM __ref("raw_orders") AS o\n' + ), + ) + seeded: subprocess.CompletedProcess[str] = run_unevaluated_rules_cli( + tmp_path, "compile", "--json" + ) + assert diagnostic_codes(seeded) == ("SQLCLEAN043",), seeded.stdout + seeded.stderr + # Replay the cached finding under the case's code: the engine cannot emit L007 yet. + cache: Path = rules_bulk_cache_path(project_dir=tmp_path, file_name="sql.json") + stored: dict[str, Any] = json.loads(cache.read_text(encoding="utf-8")) + for entry in stored["entries"].values(): + for finding in entry["findings"]: + finding["code"] = test_case.code + _ = cache.write_text(json.dumps(stored), encoding="utf-8") + + compiled: subprocess.CompletedProcess[str] = run_unevaluated_rules_cli( + tmp_path, "compile", "--json" + ) + exits: tuple[int, ...] = ( + compiled.returncode, + run_unevaluated_rules_cli(tmp_path, "rules", "run", "SQLCLEAN043").returncode, + run_unevaluated_rules_cli(tmp_path, "plan").returncode, + run_unevaluated_rules_cli(tmp_path, "build").returncode, + ) + + assert (diagnostic_codes(compiled), exits) == ( + (test_case.code,), + (test_case.expected_exit,) * len(exits), + ), compiled.stdout + compiled.stderr + + if __name__ == "__main__": pytest.main([__file__, "-vv"]) + + +@pytest.mark.parametrize( + "test_case", + ( + UnparsedDialectRulesCase( + description="a parsed dialect reports the built-in rule finding", + adapter_toml='adapter = "duckdb"\n', + expected_codes=("SQBRMODEL102",), + expected_message="SELECT * is allowed only", + expected_exit=1, + ), + UnparsedDialectRulesCase( + description="an unparsed dialect warns once per model and fails nothing", + adapter_toml=( + 'adapter = "bigquery"\ndefault_target = "dev"\n\n' + '[targets.dev]\ndatabase = "warehouse"\nschema = "analytics"\n' + ), + expected_codes=("L007", "L007"), + expected_message=( + "SQLBuild cannot parse bigquery SQL for built-in rules yet," + " so these rules were not evaluated: SQBRMODEL102" + ), + expected_exit=0, + ), + ), + ids=lambda case: case.description, +) +def test_given_project_dialect_when_running_built_in_rules_then_unparsed_dialects_only_warn( + test_case: UnparsedDialectRulesCase, tmp_path: Path +) -> None: + write_quality_project( + project_dir=tmp_path, + toml=( + 'name = "orders"\n' + test_case.adapter_toml + '\n[rules]\nselect = ["SQBRMODEL102"]\n' + ), + upstream=_RAW_HEADER + _UPSTREAM_SQL, + summary=_SUMMARY_HEADER + 'SELECT * FROM __ref("raw_orders")\n', + ) + + compiled: subprocess.CompletedProcess[str] = run_unevaluated_rules_cli( + tmp_path, "compile", "--json" + ) + ran: subprocess.CompletedProcess[str] = run_unevaluated_rules_cli( + tmp_path, "rules", "run", "SQBRMODEL102" + ) + + messages: list[str] = [ + str(item["message"]) for item in json.loads(compiled.stdout)["diagnostics"] + ] + assert ( + diagnostic_codes(compiled), + all(test_case.expected_message in message for message in messages), + (compiled.returncode, ran.returncode), + ) == ( + test_case.expected_codes, + True, + (test_case.expected_exit, test_case.expected_exit), + ), compiled.stdout + compiled.stderr + ran.stdout + ran.stderr diff --git a/tests/goldens/python_oracle/contracts_generated_enforced_implicit_typed_nullability_and_dynamic_contracts.json b/tests/goldens/python_oracle/contracts_generated_enforced_implicit_typed_nullability_and_dynamic_contracts.json index 06b2d6254c..ef13a2abfb 100644 --- a/tests/goldens/python_oracle/contracts_generated_enforced_implicit_typed_nullability_and_dynamic_contracts.json +++ b/tests/goldens/python_oracle/contracts_generated_enforced_implicit_typed_nullability_and_dynamic_contracts.json @@ -426,7 +426,7 @@ "bigquery": "0fb1be349cccda275d77113d", "databricks": "0fb1be349cccda275d77113d", "duckdb": "0fb1be349cccda275d77113d", -"postgres": "0fb1be349cccda275d77113d", +"postgres": "a281bca2dfa9d33402e687f3", "snowflake": "0fb1be349cccda275d77113d", "tsql": "0fb1be349cccda275d77113d" } @@ -594,7 +594,7 @@ "bigquery": "6e5f3057275c88687658bb88", "databricks": "6e5f3057275c88687658bb88", "duckdb": "6e5f3057275c88687658bb88", -"postgres": "6e5f3057275c88687658bb88", +"postgres": "2a42e291118d5fa419ada6b8", "snowflake": "6e5f3057275c88687658bb88", "tsql": "6e5f3057275c88687658bb88" } diff --git a/tests/goldens/python_oracle/metadata_formerly_deferred_non_ascii_names_and_comments_new_and_unknown_dialects.json b/tests/goldens/python_oracle/metadata_formerly_deferred_non_ascii_names_and_comments_new_and_unknown_dialects.json index c1e0c0cba7..ca4fc48fe6 100644 --- a/tests/goldens/python_oracle/metadata_formerly_deferred_non_ascii_names_and_comments_new_and_unknown_dialects.json +++ b/tests/goldens/python_oracle/metadata_formerly_deferred_non_ascii_names_and_comments_new_and_unknown_dialects.json @@ -3,8 +3,8 @@ "metadata", { "None": "257eadf2980dd54d687bb376", +"clickhouse": "e9bb705b46de595b24a50b7d", "duckdb": "d1d66a9dd0325471cc034afd", -"mysql": "257eadf2980dd54d687bb376", "nonsense": "6f6079f31e12e028922d40f2", "tsql": "257eadf2980dd54d687bb376" } @@ -13,8 +13,8 @@ "metadata", { "None": "c9f66e2829627aeb5e4728db", +"clickhouse": "c9f66e2829627aeb5e4728db", "duckdb": "0ed0773a2a4a6fca23c1dcac", -"mysql": "c9f66e2829627aeb5e4728db", "nonsense": "6f6079f31e12e028922d40f2", "tsql": "c9f66e2829627aeb5e4728db" } @@ -23,8 +23,8 @@ "metadata", { "None": "a86fc9ef8fd7f46e7110dff4", +"clickhouse": "a86fc9ef8fd7f46e7110dff4", "duckdb": "30c78fd5afb3d05479d6c4ca", -"mysql": "a86fc9ef8fd7f46e7110dff4", "nonsense": "6f6079f31e12e028922d40f2", "tsql": "a86fc9ef8fd7f46e7110dff4" } @@ -33,8 +33,8 @@ "metadata", { "None": "9225fd40168382c4100e98c3", +"clickhouse": "9225fd40168382c4100e98c3", "duckdb": "55da090c1795767af58252eb", -"mysql": "9225fd40168382c4100e98c3", "nonsense": "6f6079f31e12e028922d40f2", "tsql": "9225fd40168382c4100e98c3" } diff --git a/tests/goldens/python_oracle/semantic_formerly_deferred_dialects_added_to_the_native_parser_build.json b/tests/goldens/python_oracle/semantic_formerly_deferred_dialects_added_to_the_native_parser_build.json index 64b357cbe9..74d44ab9a3 100644 --- a/tests/goldens/python_oracle/semantic_formerly_deferred_dialects_added_to_the_native_parser_build.json +++ b/tests/goldens/python_oracle/semantic_formerly_deferred_dialects_added_to_the_native_parser_build.json @@ -2,22 +2,18 @@ [ "completion", { +"clickhouse": "25140233ccf8e7760c91ffd6", "databricks": "25140233ccf8e7760c91ffd6", -"mysql": "25140233ccf8e7760c91ffd6", -"oracle": "25140233ccf8e7760c91ffd6", "sqlite": "25140233ccf8e7760c91ffd6", -"trino": "25140233ccf8e7760c91ffd6", "tsql": "25140233ccf8e7760c91ffd6" } ], [ "completion", { +"clickhouse": "edd3b1ec05f9481067078045", "databricks": "edd3b1ec05f9481067078045", -"mysql": "edd3b1ec05f9481067078045", -"oracle": "edd3b1ec05f9481067078045", "sqlite": "edd3b1ec05f9481067078045", -"trino": "edd3b1ec05f9481067078045", "tsql": "edd3b1ec05f9481067078045" } ] diff --git a/tests/goldens/python_oracle/types_generated_seeded_scalar_parameterized_nested_padded_and_invalid_types.json b/tests/goldens/python_oracle/types_generated_seeded_scalar_parameterized_nested_padded_and_invalid_types.json index 1729e43aef..f2b91b5ff9 100644 --- a/tests/goldens/python_oracle/types_generated_seeded_scalar_parameterized_nested_padded_and_invalid_types.json +++ b/tests/goldens/python_oracle/types_generated_seeded_scalar_parameterized_nested_padded_and_invalid_types.json @@ -8,8 +8,8 @@ "databricks": "7e163d1c3f6840b5164def94", "duckdb": "7e163d1c3f6840b5164def94", "generic": "7e163d1c3f6840b5164def94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7e163d1c3f6840b5164def94", +"motherduck": "7e163d1c3f6840b5164def94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7e163d1c3f6840b5164def94", "postgresql": "7e163d1c3f6840b5164def94", "snowflake": "7e163d1c3f6840b5164def94", @@ -25,8 +25,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -42,10 +42,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -59,8 +59,8 @@ "databricks": "e6badc927d1adf4563b4f3a5", "duckdb": "e6badc927d1adf4563b4f3a5", "generic": "e6badc927d1adf4563b4f3a5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e6badc927d1adf4563b4f3a5", +"motherduck": "e6badc927d1adf4563b4f3a5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e6badc927d1adf4563b4f3a5", "postgresql": "e6badc927d1adf4563b4f3a5", "snowflake": "e6badc927d1adf4563b4f3a5", @@ -76,8 +76,8 @@ "databricks": "bbb2563edda71d0b69ef0855", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "bbb2563edda71d0b69ef0855", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "51d717b08676179ee1ccefe4", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "bbb2563edda71d0b69ef0855", "postgresql": "bbb2563edda71d0b69ef0855", "snowflake": "bbb2563edda71d0b69ef0855", @@ -93,8 +93,8 @@ "databricks": "688179272da645629077e980", "duckdb": "688179272da645629077e980", "generic": "688179272da645629077e980", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "688179272da645629077e980", +"motherduck": "688179272da645629077e980", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "688179272da645629077e980", "postgresql": "688179272da645629077e980", "snowflake": "b210f8eb26c37b422f786c6b", @@ -110,8 +110,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -122,34 +122,34 @@ "type", { "DuckDB": "5c8b98f516cedefc237fa721", -"None": "5c8b98f516cedefc237fa721", -"bigquery": "5c8b98f516cedefc237fa721", -"databricks": "5c8b98f516cedefc237fa721", +"None": "81a45386abae5f6be0a027d0", +"bigquery": "09b7f1f9111bc2e0767cc059", +"databricks": "81a45386abae5f6be0a027d0", "duckdb": "5c8b98f516cedefc237fa721", -"generic": "5c8b98f516cedefc237fa721", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c8b98f516cedefc237fa721", +"generic": "81a45386abae5f6be0a027d0", +"motherduck": "5c8b98f516cedefc237fa721", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c8b98f516cedefc237fa721", "postgresql": "5c8b98f516cedefc237fa721", "snowflake": "5c8b98f516cedefc237fa721", -"tsql": "5c8b98f516cedefc237fa721" +"tsql": "81a45386abae5f6be0a027d0" } ], [ "type", { -"DuckDB": "d00038134428e2ce04bc8e09", -"None": "ab7940999dc6f4c634249230", -"bigquery": "ab7940999dc6f4c634249230", -"databricks": "ab7940999dc6f4c634249230", -"duckdb": "d00038134428e2ce04bc8e09", -"generic": "ab7940999dc6f4c634249230", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab7940999dc6f4c634249230", -"postgres": "ab7940999dc6f4c634249230", -"postgresql": "ab7940999dc6f4c634249230", -"snowflake": "d00038134428e2ce04bc8e09", -"tsql": "ab7940999dc6f4c634249230" +"DuckDB": "6f9bc04f2e8c71a032255bac", +"None": "9ae2060b9503b8d23efaa736", +"bigquery": "9ae2060b9503b8d23efaa736", +"databricks": "9ae2060b9503b8d23efaa736", +"duckdb": "6f9bc04f2e8c71a032255bac", +"generic": "9ae2060b9503b8d23efaa736", +"motherduck": "6f9bc04f2e8c71a032255bac", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9ae2060b9503b8d23efaa736", +"postgresql": "9ae2060b9503b8d23efaa736", +"snowflake": "6f9bc04f2e8c71a032255bac", +"tsql": "9ae2060b9503b8d23efaa736" } ], [ @@ -161,8 +161,8 @@ "databricks": "ee1b4ffc65518c536e3632ee", "duckdb": "ee1b4ffc65518c536e3632ee", "generic": "ee1b4ffc65518c536e3632ee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ee1b4ffc65518c536e3632ee", +"motherduck": "ee1b4ffc65518c536e3632ee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ee1b4ffc65518c536e3632ee", "postgresql": "ee1b4ffc65518c536e3632ee", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -178,8 +178,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -191,12 +191,12 @@ { "DuckDB": "0f799a95a4f5ef502897f02c", "None": "0f799a95a4f5ef502897f02c", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "0f799a95a4f5ef502897f02c", "duckdb": "0f799a95a4f5ef502897f02c", "generic": "0f799a95a4f5ef502897f02c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0f799a95a4f5ef502897f02c", +"motherduck": "0f799a95a4f5ef502897f02c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0f799a95a4f5ef502897f02c", "postgresql": "0f799a95a4f5ef502897f02c", "snowflake": "0f799a95a4f5ef502897f02c", @@ -212,8 +212,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -229,8 +229,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "19bdd92ea0cc5fb028657510", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19bdd92ea0cc5fb028657510", "postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -246,10 +246,10 @@ "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", -"postgres": "18db5afe2c1e80d1ee652b54", -"postgresql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "47d64a5994d42a2b577166ea", +"postgresql": "47d64a5994d42a2b577166ea", "snowflake": "18db5afe2c1e80d1ee652b54", "tsql": "18db5afe2c1e80d1ee652b54" } @@ -259,14 +259,14 @@ { "DuckDB": "73151c1398b30e5c5d18f4ce", "None": "73151c1398b30e5c5d18f4ce", -"bigquery": "73151c1398b30e5c5d18f4ce", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "73151c1398b30e5c5d18f4ce", "duckdb": "73151c1398b30e5c5d18f4ce", "generic": "73151c1398b30e5c5d18f4ce", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73151c1398b30e5c5d18f4ce", -"postgres": "73151c1398b30e5c5d18f4ce", -"postgresql": "73151c1398b30e5c5d18f4ce", +"motherduck": "73151c1398b30e5c5d18f4ce", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f68b7dbd74ec7c3b9f3066ef", +"postgresql": "f68b7dbd74ec7c3b9f3066ef", "snowflake": "73151c1398b30e5c5d18f4ce", "tsql": "73151c1398b30e5c5d18f4ce" } @@ -275,17 +275,17 @@ "type", { "DuckDB": "3147a9e06daa6de4a4df5abc", -"None": "3147a9e06daa6de4a4df5abc", -"bigquery": "f276e907770196606577c932", -"databricks": "3147a9e06daa6de4a4df5abc", +"None": "da0625706f12b764a6912278", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "da0625706f12b764a6912278", "duckdb": "3147a9e06daa6de4a4df5abc", -"generic": "3147a9e06daa6de4a4df5abc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3147a9e06daa6de4a4df5abc", +"generic": "da0625706f12b764a6912278", +"motherduck": "3147a9e06daa6de4a4df5abc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3147a9e06daa6de4a4df5abc", "postgresql": "3147a9e06daa6de4a4df5abc", "snowflake": "67b5874a2c569226c490211f", -"tsql": "3147a9e06daa6de4a4df5abc" +"tsql": "da0625706f12b764a6912278" } ], [ @@ -297,8 +297,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -314,8 +314,8 @@ "databricks": "78b2cf275c640af54feeca31", "duckdb": "78b2cf275c640af54feeca31", "generic": "78b2cf275c640af54feeca31", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "78b2cf275c640af54feeca31", +"motherduck": "78b2cf275c640af54feeca31", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78b2cf275c640af54feeca31", "postgresql": "78b2cf275c640af54feeca31", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -331,8 +331,8 @@ "databricks": "f15c56475abfc73488abe63b", "duckdb": "f15c56475abfc73488abe63b", "generic": "f15c56475abfc73488abe63b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f15c56475abfc73488abe63b", +"motherduck": "f15c56475abfc73488abe63b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f15c56475abfc73488abe63b", "postgresql": "f15c56475abfc73488abe63b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -348,8 +348,8 @@ "databricks": "717cf79ef777823eb3576deb", "duckdb": "717cf79ef777823eb3576deb", "generic": "717cf79ef777823eb3576deb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "717cf79ef777823eb3576deb", +"motherduck": "717cf79ef777823eb3576deb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "717cf79ef777823eb3576deb", "postgresql": "717cf79ef777823eb3576deb", "snowflake": "717cf79ef777823eb3576deb", @@ -365,8 +365,8 @@ "databricks": "ea191a784359be345cd9ded6", "duckdb": "a6c6f4aa4257d422cbf3f875", "generic": "a6c6f4aa4257d422cbf3f875", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ea191a784359be345cd9ded6", +"motherduck": "a6c6f4aa4257d422cbf3f875", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a6c6f4aa4257d422cbf3f875", "postgresql": "a6c6f4aa4257d422cbf3f875", "snowflake": "a6c6f4aa4257d422cbf3f875", @@ -382,8 +382,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "78b2cf275c640af54feeca31", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78b2cf275c640af54feeca31", "postgresql": "78b2cf275c640af54feeca31", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -395,12 +395,12 @@ { "DuckDB": "0f799a95a4f5ef502897f02c", "None": "0f799a95a4f5ef502897f02c", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "0f799a95a4f5ef502897f02c", "duckdb": "0f799a95a4f5ef502897f02c", "generic": "0f799a95a4f5ef502897f02c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0f799a95a4f5ef502897f02c", +"motherduck": "0f799a95a4f5ef502897f02c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0f799a95a4f5ef502897f02c", "postgresql": "0f799a95a4f5ef502897f02c", "snowflake": "0f799a95a4f5ef502897f02c", @@ -416,10 +416,10 @@ "databricks": "e9b32e3b5a5505b32f33e169", "duckdb": "e9b32e3b5a5505b32f33e169", "generic": "e9b32e3b5a5505b32f33e169", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e9b32e3b5a5505b32f33e169", -"postgres": "e9b32e3b5a5505b32f33e169", -"postgresql": "e9b32e3b5a5505b32f33e169", +"motherduck": "e9b32e3b5a5505b32f33e169", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3fd123ac5ae610dffb11b495", +"postgresql": "3fd123ac5ae610dffb11b495", "snowflake": "e9b32e3b5a5505b32f33e169", "tsql": "e9b32e3b5a5505b32f33e169" } @@ -433,8 +433,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -450,8 +450,8 @@ "databricks": "e4f0244beaa01667e80482b6", "duckdb": "a766fea06fc070b352976b0a", "generic": "a766fea06fc070b352976b0a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e4f0244beaa01667e80482b6", +"motherduck": "a766fea06fc070b352976b0a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a766fea06fc070b352976b0a", "postgresql": "a766fea06fc070b352976b0a", "snowflake": "a766fea06fc070b352976b0a", @@ -467,8 +467,8 @@ "databricks": "4fb572024031cb7631777be1", "duckdb": "4114b18e7e26d16613331fa7", "generic": "4114b18e7e26d16613331fa7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4114b18e7e26d16613331fa7", +"motherduck": "4114b18e7e26d16613331fa7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4114b18e7e26d16613331fa7", "postgresql": "4114b18e7e26d16613331fa7", "snowflake": "4114b18e7e26d16613331fa7", @@ -484,8 +484,8 @@ "databricks": "98a80237e4e0a12f35bbf17e", "duckdb": "98a80237e4e0a12f35bbf17e", "generic": "98a80237e4e0a12f35bbf17e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "98a80237e4e0a12f35bbf17e", +"motherduck": "98a80237e4e0a12f35bbf17e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "98a80237e4e0a12f35bbf17e", "postgresql": "98a80237e4e0a12f35bbf17e", "snowflake": "98a80237e4e0a12f35bbf17e", @@ -501,8 +501,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -518,8 +518,8 @@ "databricks": "f7fca17b74cc50ae3de8c826", "duckdb": "f7fca17b74cc50ae3de8c826", "generic": "f7fca17b74cc50ae3de8c826", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f7fca17b74cc50ae3de8c826", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f7fca17b74cc50ae3de8c826", "postgresql": "f7fca17b74cc50ae3de8c826", "snowflake": "55155766823b6a3a5e882fc2", @@ -535,8 +535,8 @@ "databricks": "9a79cb58680bebe49009a72d", "duckdb": "9a79cb58680bebe49009a72d", "generic": "9a79cb58680bebe49009a72d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9a79cb58680bebe49009a72d", +"motherduck": "9a79cb58680bebe49009a72d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9a79cb58680bebe49009a72d", "postgresql": "9a79cb58680bebe49009a72d", "snowflake": "9a79cb58680bebe49009a72d", @@ -552,8 +552,8 @@ "databricks": "9bafb08d41fd1c208b5deca4", "duckdb": "9bafb08d41fd1c208b5deca4", "generic": "9bafb08d41fd1c208b5deca4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9bafb08d41fd1c208b5deca4", +"motherduck": "9bafb08d41fd1c208b5deca4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9bafb08d41fd1c208b5deca4", "postgresql": "9bafb08d41fd1c208b5deca4", "snowflake": "9bafb08d41fd1c208b5deca4", @@ -565,14 +565,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -582,12 +582,12 @@ { "DuckDB": "2e60720c3255d112fcd0938a", "None": "2e60720c3255d112fcd0938a", -"bigquery": "2e60720c3255d112fcd0938a", +"bigquery": "d65a7c279b0fc9d3804ee30e", "databricks": "2e60720c3255d112fcd0938a", "duckdb": "2e60720c3255d112fcd0938a", "generic": "2e60720c3255d112fcd0938a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2e60720c3255d112fcd0938a", +"motherduck": "2e60720c3255d112fcd0938a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2e60720c3255d112fcd0938a", "postgresql": "2e60720c3255d112fcd0938a", "snowflake": "2e60720c3255d112fcd0938a", @@ -603,10 +603,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -615,17 +615,17 @@ "type", { "DuckDB": "c9cc0c1ce817773e19f141b5", -"None": "d7249dcf128bf2bb675aa0d9", -"bigquery": "d7249dcf128bf2bb675aa0d9", -"databricks": "f1640dc6a7437eae3cc39eec", +"None": "c70e0a464fcbb5e5a1f7a8b7", +"bigquery": "c70e0a464fcbb5e5a1f7a8b7", +"databricks": "c70e0a464fcbb5e5a1f7a8b7", "duckdb": "c9cc0c1ce817773e19f141b5", -"generic": "d7249dcf128bf2bb675aa0d9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d7249dcf128bf2bb675aa0d9", -"postgres": "d7249dcf128bf2bb675aa0d9", -"postgresql": "d7249dcf128bf2bb675aa0d9", +"generic": "c70e0a464fcbb5e5a1f7a8b7", +"motherduck": "c9cc0c1ce817773e19f141b5", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c70e0a464fcbb5e5a1f7a8b7", +"postgresql": "c70e0a464fcbb5e5a1f7a8b7", "snowflake": "89091c4ddfa26794a7c15ab6", -"tsql": "d7249dcf128bf2bb675aa0d9" +"tsql": "c70e0a464fcbb5e5a1f7a8b7" } ], [ @@ -637,8 +637,8 @@ "databricks": "319a9fbb02ed503eb114164b", "duckdb": "e060b5a5a8e9774b580d58d3", "generic": "e060b5a5a8e9774b580d58d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "319a9fbb02ed503eb114164b", +"motherduck": "e060b5a5a8e9774b580d58d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e060b5a5a8e9774b580d58d3", "postgresql": "e060b5a5a8e9774b580d58d3", "snowflake": "e060b5a5a8e9774b580d58d3", @@ -654,8 +654,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -671,8 +671,8 @@ "databricks": "8111d803c9182d0189c728e9", "duckdb": "8111d803c9182d0189c728e9", "generic": "8111d803c9182d0189c728e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8111d803c9182d0189c728e9", +"motherduck": "8111d803c9182d0189c728e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8111d803c9182d0189c728e9", "postgresql": "8111d803c9182d0189c728e9", "snowflake": "8111d803c9182d0189c728e9", @@ -688,8 +688,8 @@ "databricks": "f7fca17b74cc50ae3de8c826", "duckdb": "f7fca17b74cc50ae3de8c826", "generic": "f7fca17b74cc50ae3de8c826", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f7fca17b74cc50ae3de8c826", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f7fca17b74cc50ae3de8c826", "postgresql": "f7fca17b74cc50ae3de8c826", "snowflake": "55155766823b6a3a5e882fc2", @@ -705,8 +705,8 @@ "databricks": "d79c3ac70093cb4cb531e71c", "duckdb": "d79c3ac70093cb4cb531e71c", "generic": "d79c3ac70093cb4cb531e71c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d79c3ac70093cb4cb531e71c", +"motherduck": "d79c3ac70093cb4cb531e71c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d79c3ac70093cb4cb531e71c", "postgresql": "d79c3ac70093cb4cb531e71c", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -718,14 +718,14 @@ { "DuckDB": "4b356542044f1267fcfc487a", "None": "4b356542044f1267fcfc487a", -"bigquery": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", "databricks": "4b356542044f1267fcfc487a", "duckdb": "4b356542044f1267fcfc487a", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "4b356542044f1267fcfc487a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -739,8 +739,8 @@ "databricks": "f0ec29663977eb66dca07a0e", "duckdb": "f0ec29663977eb66dca07a0e", "generic": "f0ec29663977eb66dca07a0e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f0ec29663977eb66dca07a0e", +"motherduck": "f0ec29663977eb66dca07a0e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f0ec29663977eb66dca07a0e", "postgresql": "f0ec29663977eb66dca07a0e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -752,14 +752,14 @@ { "DuckDB": "08736485547ba339930697e9", "None": "08736485547ba339930697e9", -"bigquery": "08736485547ba339930697e9", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "08736485547ba339930697e9", "duckdb": "08736485547ba339930697e9", "generic": "08736485547ba339930697e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "08736485547ba339930697e9", -"postgres": "08736485547ba339930697e9", -"postgresql": "08736485547ba339930697e9", +"motherduck": "08736485547ba339930697e9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eecd452b4f5f3f4133d0bd0a", +"postgresql": "eecd452b4f5f3f4133d0bd0a", "snowflake": "08736485547ba339930697e9", "tsql": "08736485547ba339930697e9" } @@ -773,8 +773,8 @@ "databricks": "8a3f094f18fb4047f0febf59", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "8a3f094f18fb4047f0febf59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a3f094f18fb4047f0febf59", "postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "850deea990b52b82b36a9848", @@ -790,10 +790,10 @@ "databricks": "64fc778308b798a730b1d5c9", "duckdb": "64fc778308b798a730b1d5c9", "generic": "64fc778308b798a730b1d5c9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "64fc778308b798a730b1d5c9", -"postgres": "64fc778308b798a730b1d5c9", -"postgresql": "64fc778308b798a730b1d5c9", +"motherduck": "64fc778308b798a730b1d5c9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c8ca7a940ad228dd836cad6f", +"postgresql": "c8ca7a940ad228dd836cad6f", "snowflake": "64fc778308b798a730b1d5c9", "tsql": "64fc778308b798a730b1d5c9" } @@ -807,8 +807,8 @@ "databricks": "b46dfa976d5ba04f95bea32b", "duckdb": "b46dfa976d5ba04f95bea32b", "generic": "b46dfa976d5ba04f95bea32b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b46dfa976d5ba04f95bea32b", +"motherduck": "b46dfa976d5ba04f95bea32b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "34dcdfe3e3afe6140e1353e0", "postgresql": "34dcdfe3e3afe6140e1353e0", "snowflake": "b46dfa976d5ba04f95bea32b", @@ -824,8 +824,8 @@ "databricks": "20c9f38cf0da7cc6079a3a47", "duckdb": "20c9f38cf0da7cc6079a3a47", "generic": "20c9f38cf0da7cc6079a3a47", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "20c9f38cf0da7cc6079a3a47", +"motherduck": "20c9f38cf0da7cc6079a3a47", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "20c9f38cf0da7cc6079a3a47", "postgresql": "20c9f38cf0da7cc6079a3a47", "snowflake": "20c9f38cf0da7cc6079a3a47", @@ -841,8 +841,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -858,8 +858,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -871,14 +871,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -892,8 +892,8 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19a1a80ffc06169f32694de8", "postgresql": "19a1a80ffc06169f32694de8", "snowflake": "19a1a80ffc06169f32694de8", @@ -909,8 +909,8 @@ "databricks": "6ef74921b43664a98de3f7e4", "duckdb": "09a5861e271d932cd3a9a264", "generic": "6ef74921b43664a98de3f7e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "09a5861e271d932cd3a9a264", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cc685e113aac64bd76fbc3cb", "postgresql": "cc685e113aac64bd76fbc3cb", "snowflake": "6fe4c4c52324360189048430", @@ -926,8 +926,8 @@ "databricks": "46ec3e19e05a6cb29854ccac", "duckdb": "35e216b6921bf843994f2eea", "generic": "46ec3e19e05a6cb29854ccac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "46ec3e19e05a6cb29854ccac", +"motherduck": "35e216b6921bf843994f2eea", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "46ec3e19e05a6cb29854ccac", "postgresql": "46ec3e19e05a6cb29854ccac", "snowflake": "46ec3e19e05a6cb29854ccac", @@ -943,10 +943,10 @@ "databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -960,8 +960,8 @@ "databricks": "1950e32420d1d40d41ffae64", "duckdb": "1950e32420d1d40d41ffae64", "generic": "1950e32420d1d40d41ffae64", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1950e32420d1d40d41ffae64", +"motherduck": "1950e32420d1d40d41ffae64", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1950e32420d1d40d41ffae64", "postgresql": "1950e32420d1d40d41ffae64", "snowflake": "1950e32420d1d40d41ffae64", @@ -977,8 +977,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -994,8 +994,8 @@ "databricks": "9e4a50a8aab648a9737f7d0c", "duckdb": "9e4a50a8aab648a9737f7d0c", "generic": "9e4a50a8aab648a9737f7d0c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9e4a50a8aab648a9737f7d0c", +"motherduck": "9e4a50a8aab648a9737f7d0c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "36d29258901a4d8c296baa68", "postgresql": "36d29258901a4d8c296baa68", "snowflake": "9e4a50a8aab648a9737f7d0c", @@ -1007,12 +1007,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -1028,8 +1028,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -1045,8 +1045,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -1058,14 +1058,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -1079,8 +1079,8 @@ "databricks": "a7120d1b1abcb844c87a968a", "duckdb": "a7120d1b1abcb844c87a968a", "generic": "a7120d1b1abcb844c87a968a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a7120d1b1abcb844c87a968a", +"motherduck": "a7120d1b1abcb844c87a968a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a7120d1b1abcb844c87a968a", "postgresql": "a7120d1b1abcb844c87a968a", "snowflake": "a7120d1b1abcb844c87a968a", @@ -1096,8 +1096,8 @@ "databricks": "cc2a8dbf7a9806988a7b5067", "duckdb": "cc2a8dbf7a9806988a7b5067", "generic": "cc2a8dbf7a9806988a7b5067", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cc2a8dbf7a9806988a7b5067", +"motherduck": "cc2a8dbf7a9806988a7b5067", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cc2a8dbf7a9806988a7b5067", "postgresql": "cc2a8dbf7a9806988a7b5067", "snowflake": "cc2a8dbf7a9806988a7b5067", @@ -1113,8 +1113,8 @@ "databricks": "5d4d5145235f17764810d03f", "duckdb": "5d4d5145235f17764810d03f", "generic": "5d4d5145235f17764810d03f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d4d5145235f17764810d03f", +"motherduck": "5d4d5145235f17764810d03f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5d4d5145235f17764810d03f", "postgresql": "5d4d5145235f17764810d03f", "snowflake": "5d4d5145235f17764810d03f", @@ -1130,8 +1130,8 @@ "databricks": "2427f5522575cdc3d6a74e4c", "duckdb": "2427f5522575cdc3d6a74e4c", "generic": "2427f5522575cdc3d6a74e4c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2427f5522575cdc3d6a74e4c", +"motherduck": "2427f5522575cdc3d6a74e4c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2427f5522575cdc3d6a74e4c", "postgresql": "2427f5522575cdc3d6a74e4c", "snowflake": "2427f5522575cdc3d6a74e4c", @@ -1147,8 +1147,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -1164,8 +1164,8 @@ "databricks": "12b8a08a67c6c5ca887183e3", "duckdb": "97d3bfe2b726a642c0011696", "generic": "5a48ec343f2513c62588cb55", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5a48ec343f2513c62588cb55", +"motherduck": "97d3bfe2b726a642c0011696", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5a48ec343f2513c62588cb55", "postgresql": "5a48ec343f2513c62588cb55", "snowflake": "582c76388cd6468a1e793692", @@ -1181,8 +1181,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -1198,11 +1198,11 @@ "databricks": "b5c4d5044b2a6e70633f0d0a", "duckdb": "b5c4d5044b2a6e70633f0d0a", "generic": "b5c4d5044b2a6e70633f0d0a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b5c4d5044b2a6e70633f0d0a", +"motherduck": "b5c4d5044b2a6e70633f0d0a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b5c4d5044b2a6e70633f0d0a", "postgresql": "b5c4d5044b2a6e70633f0d0a", -"snowflake": "4e24a80b20b9dee0cbbfac17", +"snowflake": "8ff614b7306c0edbe36ce00b", "tsql": "b5c4d5044b2a6e70633f0d0a" } ], @@ -1215,8 +1215,8 @@ "databricks": "ac984ee30077ed3bb3c7c9d5", "duckdb": "7a8df655ee308ea4f929b9fd", "generic": "7a8df655ee308ea4f929b9fd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ac984ee30077ed3bb3c7c9d5", +"motherduck": "7a8df655ee308ea4f929b9fd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7a8df655ee308ea4f929b9fd", "postgresql": "7a8df655ee308ea4f929b9fd", "snowflake": "7a8df655ee308ea4f929b9fd", @@ -1232,10 +1232,10 @@ "databricks": "804fec6876d0b43935790142", "duckdb": "804fec6876d0b43935790142", "generic": "804fec6876d0b43935790142", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "804fec6876d0b43935790142", -"postgres": "804fec6876d0b43935790142", -"postgresql": "804fec6876d0b43935790142", +"motherduck": "804fec6876d0b43935790142", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "191fc59c754706512337edc1", +"postgresql": "191fc59c754706512337edc1", "snowflake": "804fec6876d0b43935790142", "tsql": "804fec6876d0b43935790142" } @@ -1249,8 +1249,8 @@ "databricks": "8a4c61c12f9eaa564d1c19a1", "duckdb": "8a4c61c12f9eaa564d1c19a1", "generic": "8a4c61c12f9eaa564d1c19a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8a4c61c12f9eaa564d1c19a1", +"motherduck": "8a4c61c12f9eaa564d1c19a1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a4c61c12f9eaa564d1c19a1", "postgresql": "8a4c61c12f9eaa564d1c19a1", "snowflake": "8a4c61c12f9eaa564d1c19a1", @@ -1266,8 +1266,8 @@ "databricks": "fcb2bd160300a30539bd5398", "duckdb": "fcb2bd160300a30539bd5398", "generic": "fcb2bd160300a30539bd5398", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fcb2bd160300a30539bd5398", +"motherduck": "fcb2bd160300a30539bd5398", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fcb2bd160300a30539bd5398", "postgresql": "fcb2bd160300a30539bd5398", "snowflake": "fcb2bd160300a30539bd5398", @@ -1279,14 +1279,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -1300,8 +1300,8 @@ "databricks": "d3bcf10e00645b12a802c7d0", "duckdb": "c0eec31620dfc2ac0028173d", "generic": "d3bcf10e00645b12a802c7d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "c0eec31620dfc2ac0028173d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c0eec31620dfc2ac0028173d", "postgresql": "c0eec31620dfc2ac0028173d", "snowflake": "d379c0ee4dad89e570b7329e", @@ -1313,12 +1313,12 @@ { "DuckDB": "06aec7cca4b30489405665e2", "None": "06aec7cca4b30489405665e2", -"bigquery": "06aec7cca4b30489405665e2", +"bigquery": "96fb49e9038c050a39793710", "databricks": "06aec7cca4b30489405665e2", "duckdb": "06aec7cca4b30489405665e2", "generic": "06aec7cca4b30489405665e2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "06aec7cca4b30489405665e2", +"motherduck": "06aec7cca4b30489405665e2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "06aec7cca4b30489405665e2", "postgresql": "06aec7cca4b30489405665e2", "snowflake": "06aec7cca4b30489405665e2", @@ -1334,8 +1334,8 @@ "databricks": "76b7de42fb46036e5c9279bf", "duckdb": "76b7de42fb46036e5c9279bf", "generic": "76b7de42fb46036e5c9279bf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "76b7de42fb46036e5c9279bf", +"motherduck": "76b7de42fb46036e5c9279bf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "76b7de42fb46036e5c9279bf", "postgresql": "76b7de42fb46036e5c9279bf", "snowflake": "76b7de42fb46036e5c9279bf", @@ -1351,8 +1351,8 @@ "databricks": "93c1b5b5960e26479baf88d0", "duckdb": "93c1b5b5960e26479baf88d0", "generic": "93c1b5b5960e26479baf88d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "93c1b5b5960e26479baf88d0", +"motherduck": "93c1b5b5960e26479baf88d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "93c1b5b5960e26479baf88d0", "postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "93c1b5b5960e26479baf88d0", @@ -1368,8 +1368,8 @@ "databricks": "9db9d9c0f19fb71af06e1044", "duckdb": "3b3de43279bf3f0706595e4a", "generic": "9db9d9c0f19fb71af06e1044", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "3b3de43279bf3f0706595e4a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3b3de43279bf3f0706595e4a", "postgresql": "3b3de43279bf3f0706595e4a", "snowflake": "0b6d401f07331fac8353bbd3", @@ -1385,8 +1385,8 @@ "databricks": "6965b3d5426a0af9750f9a03", "duckdb": "6965b3d5426a0af9750f9a03", "generic": "6965b3d5426a0af9750f9a03", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6965b3d5426a0af9750f9a03", +"motherduck": "6965b3d5426a0af9750f9a03", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6965b3d5426a0af9750f9a03", "postgresql": "6965b3d5426a0af9750f9a03", "snowflake": "6965b3d5426a0af9750f9a03", @@ -1402,8 +1402,8 @@ "databricks": "7aa59eed1bba74a9dee48e59", "duckdb": "7aa59eed1bba74a9dee48e59", "generic": "7aa59eed1bba74a9dee48e59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7aa59eed1bba74a9dee48e59", +"motherduck": "7aa59eed1bba74a9dee48e59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7aa59eed1bba74a9dee48e59", "postgresql": "7aa59eed1bba74a9dee48e59", "snowflake": "7aa59eed1bba74a9dee48e59", @@ -1419,8 +1419,8 @@ "databricks": "66e7166890ccedeaad6c241a", "duckdb": "66e7166890ccedeaad6c241a", "generic": "66e7166890ccedeaad6c241a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "66e7166890ccedeaad6c241a", +"motherduck": "66e7166890ccedeaad6c241a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66e7166890ccedeaad6c241a", "postgresql": "66e7166890ccedeaad6c241a", "snowflake": "66e7166890ccedeaad6c241a", @@ -1436,8 +1436,8 @@ "databricks": "9aff7fcc7d550e1a8d105471", "duckdb": "9aff7fcc7d550e1a8d105471", "generic": "9aff7fcc7d550e1a8d105471", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9aff7fcc7d550e1a8d105471", +"motherduck": "9aff7fcc7d550e1a8d105471", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9aff7fcc7d550e1a8d105471", "postgresql": "9aff7fcc7d550e1a8d105471", "snowflake": "9aff7fcc7d550e1a8d105471", @@ -1449,12 +1449,12 @@ { "DuckDB": "b561884132dbe7e42b719110", "None": "b561884132dbe7e42b719110", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b561884132dbe7e42b719110", "postgresql": "b561884132dbe7e42b719110", "snowflake": "b561884132dbe7e42b719110", @@ -1470,10 +1470,10 @@ "databricks": "fb6eefdc9bed918e3ac82197", "duckdb": "fb6eefdc9bed918e3ac82197", "generic": "fb6eefdc9bed918e3ac82197", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fb6eefdc9bed918e3ac82197", -"postgres": "fb6eefdc9bed918e3ac82197", -"postgresql": "fb6eefdc9bed918e3ac82197", +"motherduck": "fb6eefdc9bed918e3ac82197", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "4d3368f4985b5822bc4239af", +"postgresql": "4d3368f4985b5822bc4239af", "snowflake": "fb6eefdc9bed918e3ac82197", "tsql": "fb6eefdc9bed918e3ac82197" } @@ -1481,16 +1481,16 @@ [ "type", { -"DuckDB": "8698d724534891869075175e", -"None": "0017a88bb60ec3403c786ed8", +"DuckDB": "aa7afbd57560be67e5221a3b", +"None": "47e0816fecdd9e015d7aeb79", "bigquery": "47e0816fecdd9e015d7aeb79", -"databricks": "0017a88bb60ec3403c786ed8", -"duckdb": "8698d724534891869075175e", -"generic": "0017a88bb60ec3403c786ed8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "8698d724534891869075175e", -"postgresql": "8698d724534891869075175e", +"databricks": "47e0816fecdd9e015d7aeb79", +"duckdb": "aa7afbd57560be67e5221a3b", +"generic": "47e0816fecdd9e015d7aeb79", +"motherduck": "aa7afbd57560be67e5221a3b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "aa7afbd57560be67e5221a3b", +"postgresql": "aa7afbd57560be67e5221a3b", "snowflake": "e6f36041b1d9fb8fe3a18cbb", "tsql": "d1dc9a140dbe2eb1874c6ad7" } @@ -1504,8 +1504,8 @@ "databricks": "0a22fc4bfa30bc1953d41de6", "duckdb": "ee07062f9d57cfdfb20f327b", "generic": "ee07062f9d57cfdfb20f327b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ee07062f9d57cfdfb20f327b", +"motherduck": "ee07062f9d57cfdfb20f327b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0a9a262e0d175ab0eb0865e2", "postgresql": "0a9a262e0d175ab0eb0865e2", "snowflake": "0f4d95882eb1debabb5a887b", @@ -1521,8 +1521,8 @@ "databricks": "0c43dc837a50933667588ef1", "duckdb": "0c43dc837a50933667588ef1", "generic": "0c43dc837a50933667588ef1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0c43dc837a50933667588ef1", +"motherduck": "0c43dc837a50933667588ef1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0c43dc837a50933667588ef1", "postgresql": "0c43dc837a50933667588ef1", "snowflake": "0c43dc837a50933667588ef1", @@ -1534,14 +1534,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -1555,10 +1555,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -1572,8 +1572,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -1585,14 +1585,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -1606,8 +1606,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -1623,8 +1623,8 @@ "databricks": "0c4610f182f18bf41d306e01", "duckdb": "0c4610f182f18bf41d306e01", "generic": "0c4610f182f18bf41d306e01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0c4610f182f18bf41d306e01", +"motherduck": "0c4610f182f18bf41d306e01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0c4610f182f18bf41d306e01", "postgresql": "0c4610f182f18bf41d306e01", "snowflake": "0c4610f182f18bf41d306e01", @@ -1640,8 +1640,8 @@ "databricks": "ab5ad6180652b3b00d1f5b01", "duckdb": "ab5ad6180652b3b00d1f5b01", "generic": "ab5ad6180652b3b00d1f5b01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab5ad6180652b3b00d1f5b01", +"motherduck": "ab5ad6180652b3b00d1f5b01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab5ad6180652b3b00d1f5b01", "postgresql": "ab5ad6180652b3b00d1f5b01", "snowflake": "f43dac38f34a1c0618a3f812", @@ -1657,8 +1657,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "1d8b945fd793ecdcacb90c21", "generic": "1d8b945fd793ecdcacb90c21", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1d8b945fd793ecdcacb90c21", +"motherduck": "1d8b945fd793ecdcacb90c21", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3efe8bb57c98fcbe74096b3c", "postgresql": "3efe8bb57c98fcbe74096b3c", "snowflake": "1d8b945fd793ecdcacb90c21", @@ -1670,14 +1670,14 @@ { "DuckDB": "5d25d5e13f81700741695ed4", "None": "5d25d5e13f81700741695ed4", -"bigquery": "23ee196b7bd6039855a1460f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", -"postgres": "5d25d5e13f81700741695ed4", -"postgresql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62131fb5b065f5de2e74a0f1", +"postgresql": "62131fb5b065f5de2e74a0f1", "snowflake": "5d25d5e13f81700741695ed4", "tsql": "5d25d5e13f81700741695ed4" } @@ -1691,8 +1691,8 @@ "databricks": "adbb06204fd66d8071c3dbc6", "duckdb": "29739921db4d47abc1ac18c7", "generic": "29739921db4d47abc1ac18c7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "29739921db4d47abc1ac18c7", +"motherduck": "29739921db4d47abc1ac18c7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "29739921db4d47abc1ac18c7", "postgresql": "29739921db4d47abc1ac18c7", "snowflake": "216a7f0ead009a087eea4e1c", @@ -1708,10 +1708,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -1721,14 +1721,14 @@ { "DuckDB": "ee47e1424cb01deb0cf51760", "None": "ee47e1424cb01deb0cf51760", -"bigquery": "ee47e1424cb01deb0cf51760", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "ee47e1424cb01deb0cf51760", "duckdb": "ee47e1424cb01deb0cf51760", "generic": "ee47e1424cb01deb0cf51760", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ee47e1424cb01deb0cf51760", -"postgres": "ee47e1424cb01deb0cf51760", -"postgresql": "ee47e1424cb01deb0cf51760", +"motherduck": "ee47e1424cb01deb0cf51760", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3c1521654088dd54d0dc9ead", +"postgresql": "3c1521654088dd54d0dc9ead", "snowflake": "ee47e1424cb01deb0cf51760", "tsql": "ee47e1424cb01deb0cf51760" } @@ -1742,8 +1742,8 @@ "databricks": "b4c316e81db1cb6a277b5d27", "duckdb": "1655128a72b1db9af6a89df3", "generic": "1655128a72b1db9af6a89df3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b4c316e81db1cb6a277b5d27", +"motherduck": "1655128a72b1db9af6a89df3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1655128a72b1db9af6a89df3", "postgresql": "1655128a72b1db9af6a89df3", "snowflake": "1655128a72b1db9af6a89df3", @@ -1754,17 +1754,17 @@ "type", { "DuckDB": "f2ed52f6bf46b4db9343551c", -"None": "f2ed52f6bf46b4db9343551c", -"bigquery": "f10bdd4d40eb495cbcecd31d", -"databricks": "f2ed52f6bf46b4db9343551c", +"None": "3f998a9ab68d8b2514840489", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "3f998a9ab68d8b2514840489", "duckdb": "f2ed52f6bf46b4db9343551c", -"generic": "f2ed52f6bf46b4db9343551c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f2ed52f6bf46b4db9343551c", -"postgres": "f2ed52f6bf46b4db9343551c", -"postgresql": "f2ed52f6bf46b4db9343551c", +"generic": "3f998a9ab68d8b2514840489", +"motherduck": "f2ed52f6bf46b4db9343551c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7ce41cc5375b618de22b6a4e", +"postgresql": "7ce41cc5375b618de22b6a4e", "snowflake": "f2ed52f6bf46b4db9343551c", -"tsql": "f2ed52f6bf46b4db9343551c" +"tsql": "3f998a9ab68d8b2514840489" } ], [ @@ -1776,8 +1776,8 @@ "databricks": "b46dfa976d5ba04f95bea32b", "duckdb": "b46dfa976d5ba04f95bea32b", "generic": "b46dfa976d5ba04f95bea32b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b46dfa976d5ba04f95bea32b", +"motherduck": "b46dfa976d5ba04f95bea32b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "34dcdfe3e3afe6140e1353e0", "postgresql": "34dcdfe3e3afe6140e1353e0", "snowflake": "b46dfa976d5ba04f95bea32b", @@ -1793,8 +1793,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -1810,10 +1810,10 @@ "databricks": "3e611643f0beab4953c4e09b", "duckdb": "3e611643f0beab4953c4e09b", "generic": "3e611643f0beab4953c4e09b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3e611643f0beab4953c4e09b", -"postgres": "3e611643f0beab4953c4e09b", -"postgresql": "3e611643f0beab4953c4e09b", +"motherduck": "3e611643f0beab4953c4e09b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cae477ce59c72869f37a9a40", +"postgresql": "cae477ce59c72869f37a9a40", "snowflake": "3e611643f0beab4953c4e09b", "tsql": "3e611643f0beab4953c4e09b" } @@ -1827,10 +1827,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -1840,12 +1840,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -1861,8 +1861,8 @@ "databricks": "17a6d1702d6997aa4e447f46", "duckdb": "755657fbcced1de4551fb370", "generic": "17a6d1702d6997aa4e447f46", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "17a6d1702d6997aa4e447f46", +"motherduck": "755657fbcced1de4551fb370", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "17a6d1702d6997aa4e447f46", "postgresql": "17a6d1702d6997aa4e447f46", "snowflake": "17a6d1702d6997aa4e447f46", @@ -1878,8 +1878,8 @@ "databricks": "8d66732c326e570c1efcd995", "duckdb": "8d66732c326e570c1efcd995", "generic": "8d66732c326e570c1efcd995", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8d66732c326e570c1efcd995", +"motherduck": "8d66732c326e570c1efcd995", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8d66732c326e570c1efcd995", "postgresql": "8d66732c326e570c1efcd995", "snowflake": "8d66732c326e570c1efcd995", @@ -1895,8 +1895,8 @@ "databricks": "caf173733defe796376d4efe", "duckdb": "caf173733defe796376d4efe", "generic": "caf173733defe796376d4efe", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "caf173733defe796376d4efe", +"motherduck": "caf173733defe796376d4efe", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "caf173733defe796376d4efe", "postgresql": "caf173733defe796376d4efe", "snowflake": "caf173733defe796376d4efe", @@ -1908,14 +1908,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -1926,11 +1926,11 @@ "DuckDB": "14cd6e78d75f61b2bd1bbd71", "None": "14cd6e78d75f61b2bd1bbd71", "bigquery": "14cd6e78d75f61b2bd1bbd71", -"databricks": "73504f31156e8e661c1fb26c", +"databricks": "14cd6e78d75f61b2bd1bbd71", "duckdb": "14cd6e78d75f61b2bd1bbd71", "generic": "14cd6e78d75f61b2bd1bbd71", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73504f31156e8e661c1fb26c", +"motherduck": "14cd6e78d75f61b2bd1bbd71", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "14cd6e78d75f61b2bd1bbd71", "postgresql": "14cd6e78d75f61b2bd1bbd71", "snowflake": "14cd6e78d75f61b2bd1bbd71", @@ -1946,8 +1946,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -1963,8 +1963,8 @@ "databricks": "cf5e019d9de8b46b75fdfdb6", "duckdb": "cf5e019d9de8b46b75fdfdb6", "generic": "cf5e019d9de8b46b75fdfdb6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cf5e019d9de8b46b75fdfdb6", +"motherduck": "cf5e019d9de8b46b75fdfdb6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cf5e019d9de8b46b75fdfdb6", "postgresql": "cf5e019d9de8b46b75fdfdb6", "snowflake": "cf5e019d9de8b46b75fdfdb6", @@ -1976,12 +1976,12 @@ { "DuckDB": "4f8af4650ce9c18ed0cb0bca", "None": "4f8af4650ce9c18ed0cb0bca", -"bigquery": "4f8af4650ce9c18ed0cb0bca", +"bigquery": "307965c44698c3db5bfa1db9", "databricks": "4f8af4650ce9c18ed0cb0bca", "duckdb": "4f8af4650ce9c18ed0cb0bca", "generic": "4f8af4650ce9c18ed0cb0bca", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4f8af4650ce9c18ed0cb0bca", +"motherduck": "4f8af4650ce9c18ed0cb0bca", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4f8af4650ce9c18ed0cb0bca", "postgresql": "4f8af4650ce9c18ed0cb0bca", "snowflake": "4f8af4650ce9c18ed0cb0bca", @@ -1993,12 +1993,12 @@ { "DuckDB": "cbd04e906c540e91d7c8ea88", "None": "cbd04e906c540e91d7c8ea88", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "cbd04e906c540e91d7c8ea88", "duckdb": "cbd04e906c540e91d7c8ea88", "generic": "cbd04e906c540e91d7c8ea88", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cbd04e906c540e91d7c8ea88", +"motherduck": "cbd04e906c540e91d7c8ea88", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cbd04e906c540e91d7c8ea88", "postgresql": "cbd04e906c540e91d7c8ea88", "snowflake": "cbd04e906c540e91d7c8ea88", @@ -2009,17 +2009,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -2031,10 +2031,10 @@ "databricks": "8b8d5f75ba6dec68b4cebc6b", "duckdb": "8b8d5f75ba6dec68b4cebc6b", "generic": "8b8d5f75ba6dec68b4cebc6b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8b8d5f75ba6dec68b4cebc6b", -"postgres": "8b8d5f75ba6dec68b4cebc6b", -"postgresql": "8b8d5f75ba6dec68b4cebc6b", +"motherduck": "8b8d5f75ba6dec68b4cebc6b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7914189ff18b0e70ddf7d042", +"postgresql": "7914189ff18b0e70ddf7d042", "snowflake": "8b8d5f75ba6dec68b4cebc6b", "tsql": "8b8d5f75ba6dec68b4cebc6b" } @@ -2044,12 +2044,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -2065,10 +2065,10 @@ "databricks": "38b2ec94bf02be40007ce525", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -2082,8 +2082,8 @@ "databricks": "036dd6864cf4939023240245", "duckdb": "f30e987a1e4e17305f6863ad", "generic": "036dd6864cf4939023240245", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "f30e987a1e4e17305f6863ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f30e987a1e4e17305f6863ad", "postgresql": "f30e987a1e4e17305f6863ad", "snowflake": "ff9e7821f3892ba4ddf70a84", @@ -2099,10 +2099,10 @@ "databricks": "bce60580ef3a1dc4ce569fff", "duckdb": "bce60580ef3a1dc4ce569fff", "generic": "bce60580ef3a1dc4ce569fff", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bce60580ef3a1dc4ce569fff", -"postgres": "bce60580ef3a1dc4ce569fff", -"postgresql": "bce60580ef3a1dc4ce569fff", +"motherduck": "bce60580ef3a1dc4ce569fff", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eee236ea1427bdddbaf088ac", +"postgresql": "eee236ea1427bdddbaf088ac", "snowflake": "bce60580ef3a1dc4ce569fff", "tsql": "bce60580ef3a1dc4ce569fff" } @@ -2116,10 +2116,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "19e127c23b4598727a2d8ceb", "generic": "19e127c23b4598727a2d8ceb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aed0419e7f2ce7c0a8892248", -"postgres": "19e127c23b4598727a2d8ceb", -"postgresql": "19e127c23b4598727a2d8ceb", +"motherduck": "19e127c23b4598727a2d8ceb", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "aed0419e7f2ce7c0a8892248", "tsql": "19e127c23b4598727a2d8ceb" } @@ -2133,8 +2133,8 @@ "databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "558da38ec4d9d5d6c72f60cf", "generic": "558da38ec4d9d5d6c72f60cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "558da38ec4d9d5d6c72f60cf", +"motherduck": "558da38ec4d9d5d6c72f60cf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "558da38ec4d9d5d6c72f60cf", "postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "558da38ec4d9d5d6c72f60cf", @@ -2150,8 +2150,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -2167,8 +2167,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -2184,10 +2184,10 @@ "databricks": "c6106f30f23f392e17eadef4", "duckdb": "c6106f30f23f392e17eadef4", "generic": "c6106f30f23f392e17eadef4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6106f30f23f392e17eadef4", -"postgres": "c6106f30f23f392e17eadef4", -"postgresql": "c6106f30f23f392e17eadef4", +"motherduck": "c6106f30f23f392e17eadef4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f43dac38f34a1c0618a3f812", +"postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "c6106f30f23f392e17eadef4", "tsql": "c6106f30f23f392e17eadef4" } @@ -2201,8 +2201,8 @@ "databricks": "cfb4a49a57197b0df1b2e905", "duckdb": "cfb4a49a57197b0df1b2e905", "generic": "cfb4a49a57197b0df1b2e905", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cfb4a49a57197b0df1b2e905", +"motherduck": "cfb4a49a57197b0df1b2e905", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cfb4a49a57197b0df1b2e905", "postgresql": "cfb4a49a57197b0df1b2e905", "snowflake": "cfb4a49a57197b0df1b2e905", @@ -2218,8 +2218,8 @@ "databricks": "8790273499dae4e6cbd2d034", "duckdb": "8790273499dae4e6cbd2d034", "generic": "8790273499dae4e6cbd2d034", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8790273499dae4e6cbd2d034", +"motherduck": "8790273499dae4e6cbd2d034", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8790273499dae4e6cbd2d034", "postgresql": "8790273499dae4e6cbd2d034", "snowflake": "9aff7fcc7d550e1a8d105471", @@ -2235,8 +2235,8 @@ "databricks": "e8605878c6fffc044556a858", "duckdb": "e8605878c6fffc044556a858", "generic": "e8605878c6fffc044556a858", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8605878c6fffc044556a858", +"motherduck": "e8605878c6fffc044556a858", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8605878c6fffc044556a858", "postgresql": "e8605878c6fffc044556a858", "snowflake": "e8605878c6fffc044556a858", @@ -2252,8 +2252,8 @@ "databricks": "355fafbc65133795e6c5e43b", "duckdb": "355fafbc65133795e6c5e43b", "generic": "355fafbc65133795e6c5e43b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "355fafbc65133795e6c5e43b", +"motherduck": "355fafbc65133795e6c5e43b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "355fafbc65133795e6c5e43b", "postgresql": "355fafbc65133795e6c5e43b", "snowflake": "355fafbc65133795e6c5e43b", @@ -2265,14 +2265,14 @@ { "DuckDB": "0dd2a0d5610068e51e271b9f", "None": "0dd2a0d5610068e51e271b9f", -"bigquery": "0dd2a0d5610068e51e271b9f", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "0dd2a0d5610068e51e271b9f", "duckdb": "0dd2a0d5610068e51e271b9f", "generic": "0dd2a0d5610068e51e271b9f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0dd2a0d5610068e51e271b9f", -"postgres": "0dd2a0d5610068e51e271b9f", -"postgresql": "0dd2a0d5610068e51e271b9f", +"motherduck": "0dd2a0d5610068e51e271b9f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b4df93b58afcfa82db9f544e", +"postgresql": "b4df93b58afcfa82db9f544e", "snowflake": "0dd2a0d5610068e51e271b9f", "tsql": "0dd2a0d5610068e51e271b9f" } @@ -2286,8 +2286,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -2303,8 +2303,8 @@ "databricks": "6212562845efbc753cb47471", "duckdb": "6212562845efbc753cb47471", "generic": "6212562845efbc753cb47471", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6212562845efbc753cb47471", +"motherduck": "6212562845efbc753cb47471", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6212562845efbc753cb47471", "postgresql": "6212562845efbc753cb47471", "snowflake": "6212562845efbc753cb47471", @@ -2320,8 +2320,8 @@ "databricks": "e998cd8f0f74af0526b75ff0", "duckdb": "e998cd8f0f74af0526b75ff0", "generic": "e998cd8f0f74af0526b75ff0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e998cd8f0f74af0526b75ff0", +"motherduck": "e998cd8f0f74af0526b75ff0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -2333,14 +2333,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -2354,8 +2354,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -2365,14 +2365,14 @@ [ "type", { -"DuckDB": "285056becb5d467811be7cd6", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "285056becb5d467811be7cd6", "bigquery": "0712c33a016f7dd6a37ee011", "databricks": "285056becb5d467811be7cd6", -"duckdb": "285056becb5d467811be7cd6", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "285056becb5d467811be7cd6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fc10fc558d906a80a6404b7f", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "285056becb5d467811be7cd6", "postgresql": "285056becb5d467811be7cd6", "snowflake": "0712c33a016f7dd6a37ee011", @@ -2388,8 +2388,8 @@ "databricks": "b4c316e81db1cb6a277b5d27", "duckdb": "1655128a72b1db9af6a89df3", "generic": "1655128a72b1db9af6a89df3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b4c316e81db1cb6a277b5d27", +"motherduck": "1655128a72b1db9af6a89df3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1655128a72b1db9af6a89df3", "postgresql": "1655128a72b1db9af6a89df3", "snowflake": "1655128a72b1db9af6a89df3", @@ -2405,8 +2405,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "1d8b945fd793ecdcacb90c21", "generic": "1d8b945fd793ecdcacb90c21", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1d8b945fd793ecdcacb90c21", +"motherduck": "1d8b945fd793ecdcacb90c21", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3efe8bb57c98fcbe74096b3c", "postgresql": "3efe8bb57c98fcbe74096b3c", "snowflake": "1d8b945fd793ecdcacb90c21", @@ -2422,8 +2422,8 @@ "databricks": "3cf4b9eaf303451db16b56f0", "duckdb": "3cf4b9eaf303451db16b56f0", "generic": "3cf4b9eaf303451db16b56f0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3cf4b9eaf303451db16b56f0", +"motherduck": "3cf4b9eaf303451db16b56f0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3cf4b9eaf303451db16b56f0", "postgresql": "3cf4b9eaf303451db16b56f0", "snowflake": "3cf4b9eaf303451db16b56f0", @@ -2439,8 +2439,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -2456,8 +2456,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "1e75e5227bea0ae294ac7af1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19bdd92ea0cc5fb028657510", "postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -2468,17 +2468,17 @@ "type", { "DuckDB": "d81d8c19074af0e39239164d", -"None": "1fec266134381c760a254d16", -"bigquery": "1fec266134381c760a254d16", -"databricks": "1fec266134381c760a254d16", +"None": "a52676af0c6b750acc33f021", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "a52676af0c6b750acc33f021", "duckdb": "d81d8c19074af0e39239164d", -"generic": "1fec266134381c760a254d16", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1fec266134381c760a254d16", -"postgres": "1fec266134381c760a254d16", -"postgresql": "1fec266134381c760a254d16", +"generic": "a52676af0c6b750acc33f021", +"motherduck": "d81d8c19074af0e39239164d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a52676af0c6b750acc33f021", +"postgresql": "a52676af0c6b750acc33f021", "snowflake": "1fec266134381c760a254d16", -"tsql": "1fec266134381c760a254d16" +"tsql": "a52676af0c6b750acc33f021" } ], [ @@ -2490,8 +2490,8 @@ "databricks": "3d23f811a03db519b598c815", "duckdb": "3d23f811a03db519b598c815", "generic": "3d23f811a03db519b598c815", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3d23f811a03db519b598c815", +"motherduck": "3d23f811a03db519b598c815", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3d23f811a03db519b598c815", "postgresql": "3d23f811a03db519b598c815", "snowflake": "3d23f811a03db519b598c815", @@ -2503,14 +2503,14 @@ { "DuckDB": "4928771fd9e503a30457d160", "None": "4928771fd9e503a30457d160", -"bigquery": "4928771fd9e503a30457d160", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "4928771fd9e503a30457d160", "duckdb": "4928771fd9e503a30457d160", "generic": "4928771fd9e503a30457d160", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4928771fd9e503a30457d160", -"postgres": "4928771fd9e503a30457d160", -"postgresql": "4928771fd9e503a30457d160", +"motherduck": "4928771fd9e503a30457d160", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "50ddfb168c08b09892261e82", +"postgresql": "50ddfb168c08b09892261e82", "snowflake": "4928771fd9e503a30457d160", "tsql": "4928771fd9e503a30457d160" } @@ -2524,8 +2524,8 @@ "databricks": "e280c4da8e0aefee0745af65", "duckdb": "e280c4da8e0aefee0745af65", "generic": "e280c4da8e0aefee0745af65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e280c4da8e0aefee0745af65", +"motherduck": "e280c4da8e0aefee0745af65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e280c4da8e0aefee0745af65", "postgresql": "e280c4da8e0aefee0745af65", "snowflake": "e280c4da8e0aefee0745af65", @@ -2541,8 +2541,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -2555,11 +2555,11 @@ "DuckDB": "533aa8bbcadbe01c5e7a5ba7", "None": "533aa8bbcadbe01c5e7a5ba7", "bigquery": "533aa8bbcadbe01c5e7a5ba7", -"databricks": "e8a1e0270aa8ec0f1dc6e35e", +"databricks": "533aa8bbcadbe01c5e7a5ba7", "duckdb": "533aa8bbcadbe01c5e7a5ba7", "generic": "533aa8bbcadbe01c5e7a5ba7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c619dae9f0c9870858ec6530", +"motherduck": "533aa8bbcadbe01c5e7a5ba7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "533aa8bbcadbe01c5e7a5ba7", "postgresql": "533aa8bbcadbe01c5e7a5ba7", "snowflake": "533aa8bbcadbe01c5e7a5ba7", @@ -2575,10 +2575,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -2592,8 +2592,8 @@ "databricks": "d638c2852078bf3facb9dc9e", "duckdb": "207acc46dcc150502155f4a3", "generic": "d638c2852078bf3facb9dc9e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "207acc46dcc150502155f4a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6c49aec265f259dd95b5310e", "postgresql": "6c49aec265f259dd95b5310e", "snowflake": "ef2c05747b795f92c0b523ee", @@ -2609,8 +2609,8 @@ "databricks": "ad35c4a2523ce8c2f57acf51", "duckdb": "ad35c4a2523ce8c2f57acf51", "generic": "ad35c4a2523ce8c2f57acf51", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ad35c4a2523ce8c2f57acf51", +"motherduck": "ad35c4a2523ce8c2f57acf51", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ad35c4a2523ce8c2f57acf51", "postgresql": "ad35c4a2523ce8c2f57acf51", "snowflake": "ad35c4a2523ce8c2f57acf51", @@ -2622,12 +2622,12 @@ { "DuckDB": "8203bd5901a8ec71ff7c103e", "None": "8203bd5901a8ec71ff7c103e", -"bigquery": "2a0918be53051fe629d9ff8f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "8203bd5901a8ec71ff7c103e", "duckdb": "8203bd5901a8ec71ff7c103e", "generic": "8203bd5901a8ec71ff7c103e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8203bd5901a8ec71ff7c103e", +"motherduck": "8203bd5901a8ec71ff7c103e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8203bd5901a8ec71ff7c103e", "postgresql": "8203bd5901a8ec71ff7c103e", "snowflake": "8203bd5901a8ec71ff7c103e", @@ -2643,8 +2643,8 @@ "databricks": "f38ef32a483f1c4f89ae309e", "duckdb": "f38ef32a483f1c4f89ae309e", "generic": "f38ef32a483f1c4f89ae309e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f38ef32a483f1c4f89ae309e", +"motherduck": "f38ef32a483f1c4f89ae309e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f38ef32a483f1c4f89ae309e", "postgresql": "f38ef32a483f1c4f89ae309e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -2660,8 +2660,8 @@ "databricks": "cd932c5de0731b5ff7bddd24", "duckdb": "cd932c5de0731b5ff7bddd24", "generic": "cd932c5de0731b5ff7bddd24", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cd932c5de0731b5ff7bddd24", +"motherduck": "cd932c5de0731b5ff7bddd24", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cd932c5de0731b5ff7bddd24", "postgresql": "cd932c5de0731b5ff7bddd24", "snowflake": "cd932c5de0731b5ff7bddd24", @@ -2672,17 +2672,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -2694,10 +2694,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -2711,8 +2711,8 @@ "databricks": "f38ef32a483f1c4f89ae309e", "duckdb": "f38ef32a483f1c4f89ae309e", "generic": "f38ef32a483f1c4f89ae309e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f38ef32a483f1c4f89ae309e", +"motherduck": "f38ef32a483f1c4f89ae309e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f38ef32a483f1c4f89ae309e", "postgresql": "f38ef32a483f1c4f89ae309e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -2728,8 +2728,8 @@ "databricks": "2e28de65a4b680fb5f4ebda9", "duckdb": "2e28de65a4b680fb5f4ebda9", "generic": "2e28de65a4b680fb5f4ebda9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2e28de65a4b680fb5f4ebda9", +"motherduck": "2e28de65a4b680fb5f4ebda9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2e28de65a4b680fb5f4ebda9", "postgresql": "2e28de65a4b680fb5f4ebda9", "snowflake": "2e28de65a4b680fb5f4ebda9", @@ -2745,8 +2745,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -2762,8 +2762,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "ace891b6844cb59efad42031", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "ace891b6844cb59efad42031", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -2779,8 +2779,8 @@ "databricks": "57922064a7dc546cbb50e42b", "duckdb": "57922064a7dc546cbb50e42b", "generic": "57922064a7dc546cbb50e42b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "57922064a7dc546cbb50e42b", +"motherduck": "57922064a7dc546cbb50e42b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "57922064a7dc546cbb50e42b", "postgresql": "57922064a7dc546cbb50e42b", "snowflake": "57922064a7dc546cbb50e42b", @@ -2792,12 +2792,12 @@ { "DuckDB": "e08a957777bd278f921ca0ee", "None": "e08a957777bd278f921ca0ee", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "e08a957777bd278f921ca0ee", "duckdb": "e08a957777bd278f921ca0ee", "generic": "e08a957777bd278f921ca0ee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e08a957777bd278f921ca0ee", +"motherduck": "e08a957777bd278f921ca0ee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e08a957777bd278f921ca0ee", "postgresql": "e08a957777bd278f921ca0ee", "snowflake": "e08a957777bd278f921ca0ee", @@ -2813,8 +2813,8 @@ "databricks": "2785bf366213936ce93d91f2", "duckdb": "5980124ff4dad5573f810a21", "generic": "2785bf366213936ce93d91f2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "5980124ff4dad5573f810a21", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5980124ff4dad5573f810a21", "postgresql": "5980124ff4dad5573f810a21", "snowflake": "b53e32783b8b9274e350988b", @@ -2830,8 +2830,8 @@ "databricks": "055c9340448c8378561480dd", "duckdb": "055c9340448c8378561480dd", "generic": "055c9340448c8378561480dd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "055c9340448c8378561480dd", +"motherduck": "055c9340448c8378561480dd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "055c9340448c8378561480dd", "postgresql": "055c9340448c8378561480dd", "snowflake": "055c9340448c8378561480dd", @@ -2847,8 +2847,8 @@ "databricks": "ab8c248625776c7ac340119c", "duckdb": "ab8c248625776c7ac340119c", "generic": "ab8c248625776c7ac340119c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab8c248625776c7ac340119c", +"motherduck": "ab8c248625776c7ac340119c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab8c248625776c7ac340119c", "postgresql": "ab8c248625776c7ac340119c", "snowflake": "ab8c248625776c7ac340119c", @@ -2864,10 +2864,10 @@ "databricks": "d23bf939a7eb8e49c7e6867a", "duckdb": "d23bf939a7eb8e49c7e6867a", "generic": "d23bf939a7eb8e49c7e6867a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d23bf939a7eb8e49c7e6867a", -"postgres": "d23bf939a7eb8e49c7e6867a", -"postgresql": "d23bf939a7eb8e49c7e6867a", +"motherduck": "d23bf939a7eb8e49c7e6867a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "71f4f8fbb5f5c537d895ceb4", +"postgresql": "71f4f8fbb5f5c537d895ceb4", "snowflake": "d23bf939a7eb8e49c7e6867a", "tsql": "d23bf939a7eb8e49c7e6867a" } @@ -2881,8 +2881,8 @@ "databricks": "d79c3ac70093cb4cb531e71c", "duckdb": "d79c3ac70093cb4cb531e71c", "generic": "d79c3ac70093cb4cb531e71c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d79c3ac70093cb4cb531e71c", +"motherduck": "d79c3ac70093cb4cb531e71c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d79c3ac70093cb4cb531e71c", "postgresql": "d79c3ac70093cb4cb531e71c", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -2898,8 +2898,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -2915,8 +2915,8 @@ "databricks": "782ae75a1700e1a48355b077", "duckdb": "782ae75a1700e1a48355b077", "generic": "782ae75a1700e1a48355b077", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "782ae75a1700e1a48355b077", +"motherduck": "782ae75a1700e1a48355b077", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "782ae75a1700e1a48355b077", "postgresql": "782ae75a1700e1a48355b077", "snowflake": "782ae75a1700e1a48355b077", @@ -2928,12 +2928,12 @@ { "DuckDB": "28eb0d6de9724959ce5ba43e", "None": "28eb0d6de9724959ce5ba43e", -"bigquery": "f28a96f0eb38ad8874f6ec5d", +"bigquery": "5c28019dce48a74708c90a76", "databricks": "28eb0d6de9724959ce5ba43e", "duckdb": "28eb0d6de9724959ce5ba43e", "generic": "28eb0d6de9724959ce5ba43e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28eb0d6de9724959ce5ba43e", +"motherduck": "28eb0d6de9724959ce5ba43e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "28eb0d6de9724959ce5ba43e", "postgresql": "28eb0d6de9724959ce5ba43e", "snowflake": "28eb0d6de9724959ce5ba43e", @@ -2949,8 +2949,8 @@ "databricks": "dcd1c844f2d13f3b6788d1a6", "duckdb": "dcd1c844f2d13f3b6788d1a6", "generic": "dcd1c844f2d13f3b6788d1a6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dcd1c844f2d13f3b6788d1a6", +"motherduck": "dcd1c844f2d13f3b6788d1a6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "dcd1c844f2d13f3b6788d1a6", "postgresql": "dcd1c844f2d13f3b6788d1a6", "snowflake": "dcd1c844f2d13f3b6788d1a6", @@ -2966,8 +2966,8 @@ "databricks": "304be1d51ad99bfab3750f57", "duckdb": "304be1d51ad99bfab3750f57", "generic": "304be1d51ad99bfab3750f57", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "304be1d51ad99bfab3750f57", +"motherduck": "304be1d51ad99bfab3750f57", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "304be1d51ad99bfab3750f57", "postgresql": "304be1d51ad99bfab3750f57", "snowflake": "304be1d51ad99bfab3750f57", @@ -2983,8 +2983,8 @@ "databricks": "3e42d732ee33bbd1424911a7", "duckdb": "7466f0581624f38e4d1e972d", "generic": "f25507ff493aa410f2d2a6d6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f25507ff493aa410f2d2a6d6", +"motherduck": "7466f0581624f38e4d1e972d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f25507ff493aa410f2d2a6d6", "postgresql": "f25507ff493aa410f2d2a6d6", "snowflake": "60ad8b0a5cb3592bd3b0fc8f", @@ -3000,8 +3000,8 @@ "databricks": "f4c43a87a78970bca1f88b09", "duckdb": "f4c43a87a78970bca1f88b09", "generic": "f4c43a87a78970bca1f88b09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4c43a87a78970bca1f88b09", +"motherduck": "f4c43a87a78970bca1f88b09", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c43a87a78970bca1f88b09", "postgresql": "f4c43a87a78970bca1f88b09", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -3017,8 +3017,8 @@ "databricks": "2427f5522575cdc3d6a74e4c", "duckdb": "2427f5522575cdc3d6a74e4c", "generic": "2427f5522575cdc3d6a74e4c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2427f5522575cdc3d6a74e4c", +"motherduck": "2427f5522575cdc3d6a74e4c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2427f5522575cdc3d6a74e4c", "postgresql": "2427f5522575cdc3d6a74e4c", "snowflake": "2427f5522575cdc3d6a74e4c", @@ -3029,34 +3029,34 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ "type", { "DuckDB": "3af3f9124a8649e4f72030c1", -"None": "3af3f9124a8649e4f72030c1", +"None": "8191fff105ab0743bf9ffd39", "bigquery": "8191fff105ab0743bf9ffd39", "databricks": "8191fff105ab0743bf9ffd39", "duckdb": "3af3f9124a8649e4f72030c1", -"generic": "3af3f9124a8649e4f72030c1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8191fff105ab0743bf9ffd39", -"postgres": "3af3f9124a8649e4f72030c1", -"postgresql": "3af3f9124a8649e4f72030c1", +"generic": "8191fff105ab0743bf9ffd39", +"motherduck": "3af3f9124a8649e4f72030c1", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "23cc34d931e18f55168723f0", +"postgresql": "23cc34d931e18f55168723f0", "snowflake": "3af3f9124a8649e4f72030c1", -"tsql": "3af3f9124a8649e4f72030c1" +"tsql": "8191fff105ab0743bf9ffd39" } ], [ @@ -3064,14 +3064,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -3081,12 +3081,12 @@ { "DuckDB": "5c28019dce48a74708c90a76", "None": "5c28019dce48a74708c90a76", -"bigquery": "f28a96f0eb38ad8874f6ec5d", +"bigquery": "70fb4728c3adcf6dbe4975d5", "databricks": "5c28019dce48a74708c90a76", "duckdb": "5c28019dce48a74708c90a76", "generic": "5c28019dce48a74708c90a76", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "5c28019dce48a74708c90a76", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c28019dce48a74708c90a76", "postgresql": "5c28019dce48a74708c90a76", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -3102,8 +3102,8 @@ "databricks": "0dcd6a19b7dc6f6b6d4a5006", "duckdb": "0dcd6a19b7dc6f6b6d4a5006", "generic": "0dcd6a19b7dc6f6b6d4a5006", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0dcd6a19b7dc6f6b6d4a5006", +"motherduck": "0dcd6a19b7dc6f6b6d4a5006", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0dcd6a19b7dc6f6b6d4a5006", "postgresql": "0dcd6a19b7dc6f6b6d4a5006", "snowflake": "0dcd6a19b7dc6f6b6d4a5006", @@ -3119,8 +3119,8 @@ "databricks": "c2700ca3573352821e09af03", "duckdb": "c2700ca3573352821e09af03", "generic": "c2700ca3573352821e09af03", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c2700ca3573352821e09af03", +"motherduck": "c2700ca3573352821e09af03", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c2700ca3573352821e09af03", "postgresql": "c2700ca3573352821e09af03", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -3132,14 +3132,14 @@ { "DuckDB": "b561884132dbe7e42b719110", "None": "b561884132dbe7e42b719110", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -3153,8 +3153,8 @@ "databricks": "d7b74701deb8b87c32deb7a6", "duckdb": "d7b74701deb8b87c32deb7a6", "generic": "d7b74701deb8b87c32deb7a6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d7b74701deb8b87c32deb7a6", +"motherduck": "d7b74701deb8b87c32deb7a6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d7b74701deb8b87c32deb7a6", "postgresql": "d7b74701deb8b87c32deb7a6", "snowflake": "d7b74701deb8b87c32deb7a6", @@ -3170,8 +3170,8 @@ "databricks": "a6b64a929834c52f65384447", "duckdb": "a6b64a929834c52f65384447", "generic": "a6b64a929834c52f65384447", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a6b64a929834c52f65384447", +"motherduck": "a6b64a929834c52f65384447", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a6b64a929834c52f65384447", "postgresql": "a6b64a929834c52f65384447", "snowflake": "a6b64a929834c52f65384447", @@ -3182,17 +3182,17 @@ "type", { "DuckDB": "d81d8c19074af0e39239164d", -"None": "1fec266134381c760a254d16", -"bigquery": "1fec266134381c760a254d16", -"databricks": "1fec266134381c760a254d16", +"None": "a52676af0c6b750acc33f021", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "a52676af0c6b750acc33f021", "duckdb": "d81d8c19074af0e39239164d", -"generic": "1fec266134381c760a254d16", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1fec266134381c760a254d16", -"postgres": "1fec266134381c760a254d16", -"postgresql": "1fec266134381c760a254d16", +"generic": "a52676af0c6b750acc33f021", +"motherduck": "d81d8c19074af0e39239164d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a52676af0c6b750acc33f021", +"postgresql": "a52676af0c6b750acc33f021", "snowflake": "1fec266134381c760a254d16", -"tsql": "1fec266134381c760a254d16" +"tsql": "a52676af0c6b750acc33f021" } ], [ @@ -3200,12 +3200,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -3221,8 +3221,8 @@ "databricks": "fbbf6dbffd81c1b899b2f41b", "duckdb": "fbbf6dbffd81c1b899b2f41b", "generic": "fbbf6dbffd81c1b899b2f41b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fbbf6dbffd81c1b899b2f41b", +"motherduck": "fbbf6dbffd81c1b899b2f41b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fbbf6dbffd81c1b899b2f41b", "postgresql": "fbbf6dbffd81c1b899b2f41b", "snowflake": "fbbf6dbffd81c1b899b2f41b", @@ -3238,8 +3238,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -3255,8 +3255,8 @@ "databricks": "a2c63b7e92b578944818d750", "duckdb": "a2c63b7e92b578944818d750", "generic": "a2c63b7e92b578944818d750", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a2c63b7e92b578944818d750", +"motherduck": "a2c63b7e92b578944818d750", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a2c63b7e92b578944818d750", "postgresql": "a2c63b7e92b578944818d750", "snowflake": "a2c63b7e92b578944818d750", @@ -3272,8 +3272,8 @@ "databricks": "a3d6f52ec64a728636793faa", "duckdb": "a3d6f52ec64a728636793faa", "generic": "a3d6f52ec64a728636793faa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a3d6f52ec64a728636793faa", +"motherduck": "a3d6f52ec64a728636793faa", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a3d6f52ec64a728636793faa", "postgresql": "a3d6f52ec64a728636793faa", "snowflake": "a3d6f52ec64a728636793faa", @@ -3289,8 +3289,8 @@ "databricks": "dcb00b7053ffe69fe3ce99cd", "duckdb": "c7b697bbfa82dd2821ad64e9", "generic": "c7b697bbfa82dd2821ad64e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dcb00b7053ffe69fe3ce99cd", +"motherduck": "c7b697bbfa82dd2821ad64e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c7b697bbfa82dd2821ad64e9", "postgresql": "c7b697bbfa82dd2821ad64e9", "snowflake": "c7b697bbfa82dd2821ad64e9", @@ -3306,8 +3306,8 @@ "databricks": "717cf79ef777823eb3576deb", "duckdb": "717cf79ef777823eb3576deb", "generic": "717cf79ef777823eb3576deb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "717cf79ef777823eb3576deb", +"motherduck": "717cf79ef777823eb3576deb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "717cf79ef777823eb3576deb", "postgresql": "717cf79ef777823eb3576deb", "snowflake": "717cf79ef777823eb3576deb", @@ -3323,8 +3323,8 @@ "databricks": "c1dad3a1ff61e56cb10b9cdf", "duckdb": "c1dad3a1ff61e56cb10b9cdf", "generic": "c1dad3a1ff61e56cb10b9cdf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c1dad3a1ff61e56cb10b9cdf", +"motherduck": "c1dad3a1ff61e56cb10b9cdf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c1dad3a1ff61e56cb10b9cdf", "postgresql": "c1dad3a1ff61e56cb10b9cdf", "snowflake": "c1dad3a1ff61e56cb10b9cdf", @@ -3340,8 +3340,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -3357,8 +3357,8 @@ "databricks": "17a6d1702d6997aa4e447f46", "duckdb": "755657fbcced1de4551fb370", "generic": "17a6d1702d6997aa4e447f46", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "17a6d1702d6997aa4e447f46", +"motherduck": "755657fbcced1de4551fb370", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "17a6d1702d6997aa4e447f46", "postgresql": "17a6d1702d6997aa4e447f46", "snowflake": "17a6d1702d6997aa4e447f46", @@ -3374,8 +3374,8 @@ "databricks": "c05190b1f2b5d7003e7076da", "duckdb": "c05190b1f2b5d7003e7076da", "generic": "c05190b1f2b5d7003e7076da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c05190b1f2b5d7003e7076da", +"motherduck": "c05190b1f2b5d7003e7076da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c05190b1f2b5d7003e7076da", "postgresql": "c05190b1f2b5d7003e7076da", "snowflake": "c05190b1f2b5d7003e7076da", @@ -3386,17 +3386,17 @@ "type", { "DuckDB": "fa45b7e4a9ef3b5a9c5781af", -"None": "fa45b7e4a9ef3b5a9c5781af", -"bigquery": "fa45b7e4a9ef3b5a9c5781af", -"databricks": "fa45b7e4a9ef3b5a9c5781af", +"None": "ebe2513b6cb4001e42cc464b", +"bigquery": "0c43dc837a50933667588ef1", +"databricks": "ebe2513b6cb4001e42cc464b", "duckdb": "fa45b7e4a9ef3b5a9c5781af", -"generic": "fa45b7e4a9ef3b5a9c5781af", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fa45b7e4a9ef3b5a9c5781af", +"generic": "ebe2513b6cb4001e42cc464b", +"motherduck": "fa45b7e4a9ef3b5a9c5781af", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fa45b7e4a9ef3b5a9c5781af", "postgresql": "fa45b7e4a9ef3b5a9c5781af", "snowflake": "fa45b7e4a9ef3b5a9c5781af", -"tsql": "fa45b7e4a9ef3b5a9c5781af" +"tsql": "ebe2513b6cb4001e42cc464b" } ], [ @@ -3408,8 +3408,8 @@ "databricks": "1bdf5908ca249b2331a0427c", "duckdb": "1bdf5908ca249b2331a0427c", "generic": "1bdf5908ca249b2331a0427c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1bdf5908ca249b2331a0427c", +"motherduck": "1bdf5908ca249b2331a0427c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1bdf5908ca249b2331a0427c", "postgresql": "1bdf5908ca249b2331a0427c", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -3425,8 +3425,8 @@ "databricks": "bb7975dcb1f2ac7cdd149b0b", "duckdb": "bb7975dcb1f2ac7cdd149b0b", "generic": "bb7975dcb1f2ac7cdd149b0b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bb7975dcb1f2ac7cdd149b0b", +"motherduck": "bb7975dcb1f2ac7cdd149b0b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "bb7975dcb1f2ac7cdd149b0b", "postgresql": "bb7975dcb1f2ac7cdd149b0b", "snowflake": "bb7975dcb1f2ac7cdd149b0b", @@ -3442,8 +3442,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -3459,8 +3459,8 @@ "databricks": "74ed18299daa6faf5c9533e2", "duckdb": "74ed18299daa6faf5c9533e2", "generic": "74ed18299daa6faf5c9533e2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "74ed18299daa6faf5c9533e2", +"motherduck": "74ed18299daa6faf5c9533e2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "74ed18299daa6faf5c9533e2", "postgresql": "74ed18299daa6faf5c9533e2", "snowflake": "74ed18299daa6faf5c9533e2", @@ -3476,8 +3476,8 @@ "databricks": "60a23b7dcae6c79f4de83068", "duckdb": "60a23b7dcae6c79f4de83068", "generic": "60a23b7dcae6c79f4de83068", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60a23b7dcae6c79f4de83068", +"motherduck": "60a23b7dcae6c79f4de83068", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "60a23b7dcae6c79f4de83068", "postgresql": "60a23b7dcae6c79f4de83068", "snowflake": "60a23b7dcae6c79f4de83068", @@ -3489,12 +3489,12 @@ { "DuckDB": "0f799a95a4f5ef502897f02c", "None": "0f799a95a4f5ef502897f02c", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "0f799a95a4f5ef502897f02c", "duckdb": "0f799a95a4f5ef502897f02c", "generic": "0f799a95a4f5ef502897f02c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0f799a95a4f5ef502897f02c", +"motherduck": "0f799a95a4f5ef502897f02c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0f799a95a4f5ef502897f02c", "postgresql": "0f799a95a4f5ef502897f02c", "snowflake": "0f799a95a4f5ef502897f02c", @@ -3510,8 +3510,8 @@ "databricks": "5d0a19a893dbf0c1e6e9f15c", "duckdb": "5d0a19a893dbf0c1e6e9f15c", "generic": "5d0a19a893dbf0c1e6e9f15c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d0a19a893dbf0c1e6e9f15c", +"motherduck": "5d0a19a893dbf0c1e6e9f15c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5d0a19a893dbf0c1e6e9f15c", "postgresql": "5d0a19a893dbf0c1e6e9f15c", "snowflake": "5d0a19a893dbf0c1e6e9f15c", @@ -3523,14 +3523,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -3540,12 +3540,12 @@ { "DuckDB": "855e2727b4a8cb5d2711f102", "None": "855e2727b4a8cb5d2711f102", -"bigquery": "855e2727b4a8cb5d2711f102", +"bigquery": "01e56f2a28ba32841a6d614a", "databricks": "855e2727b4a8cb5d2711f102", "duckdb": "855e2727b4a8cb5d2711f102", "generic": "855e2727b4a8cb5d2711f102", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "855e2727b4a8cb5d2711f102", +"motherduck": "855e2727b4a8cb5d2711f102", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "855e2727b4a8cb5d2711f102", "postgresql": "855e2727b4a8cb5d2711f102", "snowflake": "855e2727b4a8cb5d2711f102", @@ -3556,17 +3556,17 @@ "type", { "DuckDB": "1ebac487198b6c90b027faab", -"None": "1ebac487198b6c90b027faab", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "1ebac487198b6c90b027faab", +"None": "79278352c17c9de0f5a28af5", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "79278352c17c9de0f5a28af5", "duckdb": "1ebac487198b6c90b027faab", -"generic": "1ebac487198b6c90b027faab", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1ebac487198b6c90b027faab", -"postgres": "1ebac487198b6c90b027faab", -"postgresql": "1ebac487198b6c90b027faab", -"snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "c692d974ee3f362ecb38f27c" +"generic": "79278352c17c9de0f5a28af5", +"motherduck": "1ebac487198b6c90b027faab", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "79278352c17c9de0f5a28af5", +"postgresql": "79278352c17c9de0f5a28af5", +"snowflake": "97a20344d811fb77c7690863", +"tsql": "79278352c17c9de0f5a28af5" } ], [ @@ -3575,11 +3575,11 @@ "DuckDB": "9c2cff8be7225cf79883ca65", "None": "9c2cff8be7225cf79883ca65", "bigquery": "9c2cff8be7225cf79883ca65", -"databricks": "901ac89f8d9ec18bd3a61e4b", +"databricks": "9c2cff8be7225cf79883ca65", "duckdb": "9c2cff8be7225cf79883ca65", "generic": "9c2cff8be7225cf79883ca65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "901ac89f8d9ec18bd3a61e4b", +"motherduck": "9c2cff8be7225cf79883ca65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9c2cff8be7225cf79883ca65", "postgresql": "9c2cff8be7225cf79883ca65", "snowflake": "9c2cff8be7225cf79883ca65", @@ -3595,8 +3595,8 @@ "databricks": "74f50f8ec759a3cc65ac027d", "duckdb": "74f50f8ec759a3cc65ac027d", "generic": "74f50f8ec759a3cc65ac027d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "74f50f8ec759a3cc65ac027d", +"motherduck": "74f50f8ec759a3cc65ac027d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "74f50f8ec759a3cc65ac027d", "postgresql": "74f50f8ec759a3cc65ac027d", "snowflake": "74f50f8ec759a3cc65ac027d", @@ -3612,8 +3612,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -3629,8 +3629,8 @@ "databricks": "0dcd6a19b7dc6f6b6d4a5006", "duckdb": "0dcd6a19b7dc6f6b6d4a5006", "generic": "0dcd6a19b7dc6f6b6d4a5006", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0dcd6a19b7dc6f6b6d4a5006", +"motherduck": "0dcd6a19b7dc6f6b6d4a5006", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0dcd6a19b7dc6f6b6d4a5006", "postgresql": "0dcd6a19b7dc6f6b6d4a5006", "snowflake": "0dcd6a19b7dc6f6b6d4a5006", @@ -3646,8 +3646,8 @@ "databricks": "7ffb4db35f5f9871e8c9a084", "duckdb": "7ffb4db35f5f9871e8c9a084", "generic": "7ffb4db35f5f9871e8c9a084", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7ffb4db35f5f9871e8c9a084", +"motherduck": "7ffb4db35f5f9871e8c9a084", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7ffb4db35f5f9871e8c9a084", "postgresql": "7ffb4db35f5f9871e8c9a084", "snowflake": "7ffb4db35f5f9871e8c9a084", @@ -3663,8 +3663,8 @@ "databricks": "d79c3ac70093cb4cb531e71c", "duckdb": "d79c3ac70093cb4cb531e71c", "generic": "d79c3ac70093cb4cb531e71c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d79c3ac70093cb4cb531e71c", +"motherduck": "d79c3ac70093cb4cb531e71c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d79c3ac70093cb4cb531e71c", "postgresql": "d79c3ac70093cb4cb531e71c", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -3675,17 +3675,17 @@ "type", { "DuckDB": "3147a9e06daa6de4a4df5abc", -"None": "3147a9e06daa6de4a4df5abc", -"bigquery": "f276e907770196606577c932", -"databricks": "3147a9e06daa6de4a4df5abc", +"None": "da0625706f12b764a6912278", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "da0625706f12b764a6912278", "duckdb": "3147a9e06daa6de4a4df5abc", -"generic": "3147a9e06daa6de4a4df5abc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3147a9e06daa6de4a4df5abc", +"generic": "da0625706f12b764a6912278", +"motherduck": "3147a9e06daa6de4a4df5abc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3147a9e06daa6de4a4df5abc", "postgresql": "3147a9e06daa6de4a4df5abc", "snowflake": "67b5874a2c569226c490211f", -"tsql": "3147a9e06daa6de4a4df5abc" +"tsql": "da0625706f12b764a6912278" } ], [ @@ -3697,8 +3697,8 @@ "databricks": "02e0b2ed25f32a8d09350a23", "duckdb": "7afe04283391fb5bd9bd1f6e", "generic": "6fad076e9d13c0d09c490402", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "7afe04283391fb5bd9bd1f6e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7afe04283391fb5bd9bd1f6e", "postgresql": "7afe04283391fb5bd9bd1f6e", "snowflake": "dddc37362726cca4f2353a8d", @@ -3714,8 +3714,8 @@ "databricks": "802f1c1fcb97ddd5679172d2", "duckdb": "802f1c1fcb97ddd5679172d2", "generic": "802f1c1fcb97ddd5679172d2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "802f1c1fcb97ddd5679172d2", +"motherduck": "802f1c1fcb97ddd5679172d2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "802f1c1fcb97ddd5679172d2", "postgresql": "802f1c1fcb97ddd5679172d2", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -3731,8 +3731,8 @@ "databricks": "7552f1f36764fa5333dc3b01", "duckdb": "71cce31a35d7039dcd2aa86d", "generic": "71cce31a35d7039dcd2aa86d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "71cce31a35d7039dcd2aa86d", +"motherduck": "71cce31a35d7039dcd2aa86d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "71cce31a35d7039dcd2aa86d", "postgresql": "71cce31a35d7039dcd2aa86d", "snowflake": "aa1ac5b2c999b497d54fa520", @@ -3744,12 +3744,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -3765,8 +3765,8 @@ "databricks": "4169d9651c2a3667fc561f32", "duckdb": "4169d9651c2a3667fc561f32", "generic": "4169d9651c2a3667fc561f32", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4169d9651c2a3667fc561f32", +"motherduck": "4169d9651c2a3667fc561f32", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4169d9651c2a3667fc561f32", "postgresql": "4169d9651c2a3667fc561f32", "snowflake": "b210f8eb26c37b422f786c6b", @@ -3782,8 +3782,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -3799,8 +3799,8 @@ "databricks": "6533f23a0660791970dbbbc8", "duckdb": "6533f23a0660791970dbbbc8", "generic": "6533f23a0660791970dbbbc8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6533f23a0660791970dbbbc8", +"motherduck": "6533f23a0660791970dbbbc8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6533f23a0660791970dbbbc8", "postgresql": "6533f23a0660791970dbbbc8", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -3816,8 +3816,8 @@ "databricks": "6d07fc2f489d414e7eb90262", "duckdb": "9d0ae138704cb97e3fe4d105", "generic": "9d0ae138704cb97e3fe4d105", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6d07fc2f489d414e7eb90262", +"motherduck": "9d0ae138704cb97e3fe4d105", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9d0ae138704cb97e3fe4d105", "postgresql": "9d0ae138704cb97e3fe4d105", "snowflake": "9d0ae138704cb97e3fe4d105", @@ -3829,14 +3829,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -3845,34 +3845,34 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ "type", { "DuckDB": "2c44dc35fd1ef8701101802e", -"None": "2c44dc35fd1ef8701101802e", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "2c44dc35fd1ef8701101802e", +"None": "8441d00030df06ad6a5534d3", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "8441d00030df06ad6a5534d3", "duckdb": "2c44dc35fd1ef8701101802e", -"generic": "2c44dc35fd1ef8701101802e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c44dc35fd1ef8701101802e", -"postgres": "2c44dc35fd1ef8701101802e", -"postgresql": "2c44dc35fd1ef8701101802e", +"generic": "8441d00030df06ad6a5534d3", +"motherduck": "2c44dc35fd1ef8701101802e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8441d00030df06ad6a5534d3", +"postgresql": "8441d00030df06ad6a5534d3", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "2c44dc35fd1ef8701101802e" +"tsql": "8441d00030df06ad6a5534d3" } ], [ @@ -3884,8 +3884,8 @@ "databricks": "9917d6c8c04307304904313b", "duckdb": "9917d6c8c04307304904313b", "generic": "9917d6c8c04307304904313b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9917d6c8c04307304904313b", +"motherduck": "9917d6c8c04307304904313b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9917d6c8c04307304904313b", "postgresql": "9917d6c8c04307304904313b", "snowflake": "9917d6c8c04307304904313b", @@ -3896,17 +3896,17 @@ "type", { "DuckDB": "8a3f094f18fb4047f0febf59", -"None": "e51e1eecca3e36c76e34498f", -"bigquery": "5c28019dce48a74708c90a76", -"databricks": "e51e1eecca3e36c76e34498f", +"None": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", +"databricks": "38b2ec94bf02be40007ce525", "duckdb": "8a3f094f18fb4047f0febf59", -"generic": "e51e1eecca3e36c76e34498f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "84fca9ac893777959cfd715c", -"postgres": "e51e1eecca3e36c76e34498f", -"postgresql": "e51e1eecca3e36c76e34498f", +"generic": "38b2ec94bf02be40007ce525", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "e51e1eecca3e36c76e34498f", -"tsql": "8adf69b4c11191257f2953cc" +"tsql": "38b2ec94bf02be40007ce525" } ], [ @@ -3914,14 +3914,14 @@ { "DuckDB": "4b356542044f1267fcfc487a", "None": "4b356542044f1267fcfc487a", -"bigquery": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", "databricks": "4b356542044f1267fcfc487a", "duckdb": "4b356542044f1267fcfc487a", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "4b356542044f1267fcfc487a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -3935,8 +3935,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -3948,14 +3948,14 @@ { "DuckDB": "afc451102e0ebe7f507119b9", "None": "afc451102e0ebe7f507119b9", -"bigquery": "afc451102e0ebe7f507119b9", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "afc451102e0ebe7f507119b9", "duckdb": "afc451102e0ebe7f507119b9", "generic": "afc451102e0ebe7f507119b9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "afc451102e0ebe7f507119b9", -"postgres": "afc451102e0ebe7f507119b9", -"postgresql": "afc451102e0ebe7f507119b9", +"motherduck": "afc451102e0ebe7f507119b9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ff3f47e5455500158628a360", +"postgresql": "ff3f47e5455500158628a360", "snowflake": "afc451102e0ebe7f507119b9", "tsql": "afc451102e0ebe7f507119b9" } @@ -3969,8 +3969,8 @@ "databricks": "74ed18299daa6faf5c9533e2", "duckdb": "74ed18299daa6faf5c9533e2", "generic": "74ed18299daa6faf5c9533e2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "74ed18299daa6faf5c9533e2", +"motherduck": "74ed18299daa6faf5c9533e2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "74ed18299daa6faf5c9533e2", "postgresql": "74ed18299daa6faf5c9533e2", "snowflake": "74ed18299daa6faf5c9533e2", @@ -3986,8 +3986,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -4003,8 +4003,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -4016,14 +4016,14 @@ { "DuckDB": "60b39a68a18438603dd5496d", "None": "60b39a68a18438603dd5496d", -"bigquery": "60b39a68a18438603dd5496d", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "60b39a68a18438603dd5496d", "duckdb": "60b39a68a18438603dd5496d", "generic": "60b39a68a18438603dd5496d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60b39a68a18438603dd5496d", -"postgres": "60b39a68a18438603dd5496d", -"postgresql": "60b39a68a18438603dd5496d", +"motherduck": "60b39a68a18438603dd5496d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7a007e6ff9e9465c9d7fadea", +"postgresql": "7a007e6ff9e9465c9d7fadea", "snowflake": "60b39a68a18438603dd5496d", "tsql": "60b39a68a18438603dd5496d" } @@ -4037,10 +4037,10 @@ "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -4054,8 +4054,8 @@ "databricks": "0b9d9879e289ea9a6a3e3e70", "duckdb": "0b9d9879e289ea9a6a3e3e70", "generic": "0b9d9879e289ea9a6a3e3e70", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0b9d9879e289ea9a6a3e3e70", +"motherduck": "0b9d9879e289ea9a6a3e3e70", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0b9d9879e289ea9a6a3e3e70", "postgresql": "0b9d9879e289ea9a6a3e3e70", "snowflake": "0b9d9879e289ea9a6a3e3e70", @@ -4071,8 +4071,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -4088,8 +4088,8 @@ "databricks": "c6197f1da8168ac56d61807b", "duckdb": "c6197f1da8168ac56d61807b", "generic": "c6197f1da8168ac56d61807b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6197f1da8168ac56d61807b", +"motherduck": "c6197f1da8168ac56d61807b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6197f1da8168ac56d61807b", "postgresql": "c6197f1da8168ac56d61807b", "snowflake": "c6197f1da8168ac56d61807b", @@ -4101,14 +4101,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -4122,8 +4122,8 @@ "databricks": "e998cd8f0f74af0526b75ff0", "duckdb": "e998cd8f0f74af0526b75ff0", "generic": "e998cd8f0f74af0526b75ff0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e998cd8f0f74af0526b75ff0", +"motherduck": "e998cd8f0f74af0526b75ff0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -4139,8 +4139,8 @@ "databricks": "58b79aaeb9587eafe21bc119", "duckdb": "58b79aaeb9587eafe21bc119", "generic": "58b79aaeb9587eafe21bc119", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "58b79aaeb9587eafe21bc119", +"motherduck": "58b79aaeb9587eafe21bc119", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "58b79aaeb9587eafe21bc119", "postgresql": "58b79aaeb9587eafe21bc119", "snowflake": "58b79aaeb9587eafe21bc119", @@ -4152,14 +4152,14 @@ { "DuckDB": "776e4bf23c8ef10ffaceade2", "None": "776e4bf23c8ef10ffaceade2", -"bigquery": "776e4bf23c8ef10ffaceade2", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "776e4bf23c8ef10ffaceade2", "duckdb": "776e4bf23c8ef10ffaceade2", "generic": "776e4bf23c8ef10ffaceade2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "776e4bf23c8ef10ffaceade2", -"postgres": "776e4bf23c8ef10ffaceade2", -"postgresql": "776e4bf23c8ef10ffaceade2", +"motherduck": "776e4bf23c8ef10ffaceade2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5d06b10843e3e6487fb9906a", +"postgresql": "5d06b10843e3e6487fb9906a", "snowflake": "776e4bf23c8ef10ffaceade2", "tsql": "776e4bf23c8ef10ffaceade2" } @@ -4173,8 +4173,8 @@ "databricks": "73d31c6e78cdc3b595b9c4e9", "duckdb": "73d31c6e78cdc3b595b9c4e9", "generic": "73d31c6e78cdc3b595b9c4e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73d31c6e78cdc3b595b9c4e9", +"motherduck": "73d31c6e78cdc3b595b9c4e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "73d31c6e78cdc3b595b9c4e9", "postgresql": "73d31c6e78cdc3b595b9c4e9", "snowflake": "73d31c6e78cdc3b595b9c4e9", @@ -4190,8 +4190,8 @@ "databricks": "57922064a7dc546cbb50e42b", "duckdb": "57922064a7dc546cbb50e42b", "generic": "57922064a7dc546cbb50e42b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "57922064a7dc546cbb50e42b", +"motherduck": "57922064a7dc546cbb50e42b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "57922064a7dc546cbb50e42b", "postgresql": "57922064a7dc546cbb50e42b", "snowflake": "57922064a7dc546cbb50e42b", @@ -4203,14 +4203,14 @@ { "DuckDB": "e408f2d8251e1675179187e6", "None": "e408f2d8251e1675179187e6", -"bigquery": "e408f2d8251e1675179187e6", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "e408f2d8251e1675179187e6", "duckdb": "e408f2d8251e1675179187e6", "generic": "e408f2d8251e1675179187e6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e408f2d8251e1675179187e6", -"postgres": "e408f2d8251e1675179187e6", -"postgresql": "e408f2d8251e1675179187e6", +"motherduck": "e408f2d8251e1675179187e6", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "04bfa329b4dc3b5c43ad2560", +"postgresql": "04bfa329b4dc3b5c43ad2560", "snowflake": "e408f2d8251e1675179187e6", "tsql": "e408f2d8251e1675179187e6" } @@ -4220,14 +4220,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -4241,8 +4241,8 @@ "databricks": "5c528c7b4329aeb692ec6e94", "duckdb": "5c528c7b4329aeb692ec6e94", "generic": "5c528c7b4329aeb692ec6e94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c528c7b4329aeb692ec6e94", +"motherduck": "5c528c7b4329aeb692ec6e94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c528c7b4329aeb692ec6e94", "postgresql": "5c528c7b4329aeb692ec6e94", "snowflake": "5c528c7b4329aeb692ec6e94", @@ -4258,8 +4258,8 @@ "databricks": "96ecb0bd0c27167bf4e31a08", "duckdb": "96ecb0bd0c27167bf4e31a08", "generic": "96ecb0bd0c27167bf4e31a08", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "96ecb0bd0c27167bf4e31a08", +"motherduck": "96ecb0bd0c27167bf4e31a08", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "96ecb0bd0c27167bf4e31a08", "postgresql": "96ecb0bd0c27167bf4e31a08", "snowflake": "96ecb0bd0c27167bf4e31a08", @@ -4275,8 +4275,8 @@ "databricks": "b46dfa976d5ba04f95bea32b", "duckdb": "b46dfa976d5ba04f95bea32b", "generic": "b46dfa976d5ba04f95bea32b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b46dfa976d5ba04f95bea32b", +"motherduck": "b46dfa976d5ba04f95bea32b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "34dcdfe3e3afe6140e1353e0", "postgresql": "34dcdfe3e3afe6140e1353e0", "snowflake": "b46dfa976d5ba04f95bea32b", @@ -4287,51 +4287,51 @@ "type", { "DuckDB": "f8262a8fca64e4491f151610", -"None": "dee6957f9182718819d5f721", -"bigquery": "c9c3ab06d6a47f6501ca2f3f", -"databricks": "47f8ea982c36ab449140ac7a", +"None": "fbcb21692163c35ec690c1a4", +"bigquery": "c74425c5ce650ac69c83ca5b", +"databricks": "fbcb21692163c35ec690c1a4", "duckdb": "f8262a8fca64e4491f151610", -"generic": "dee6957f9182718819d5f721", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dee6957f9182718819d5f721", -"postgres": "5bc5cf0052fdc17435b19fc2", -"postgresql": "5bc5cf0052fdc17435b19fc2", +"generic": "fbcb21692163c35ec690c1a4", +"motherduck": "f8262a8fca64e4491f151610", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "fbcb21692163c35ec690c1a4", +"postgresql": "fbcb21692163c35ec690c1a4", "snowflake": "f8262a8fca64e4491f151610", -"tsql": "dee6957f9182718819d5f721" +"tsql": "fbcb21692163c35ec690c1a4" } ], [ "type", { "DuckDB": "b694aa93969368381cb9d9bd", -"None": "b694aa93969368381cb9d9bd", -"bigquery": "df6620c838173adb34d41a29", -"databricks": "b694aa93969368381cb9d9bd", +"None": "fdd892c7bbc36567f9619175", +"bigquery": "3c0719ca5c83e0a20d0b0b42", +"databricks": "fdd892c7bbc36567f9619175", "duckdb": "b694aa93969368381cb9d9bd", -"generic": "b694aa93969368381cb9d9bd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b694aa93969368381cb9d9bd", +"generic": "fdd892c7bbc36567f9619175", +"motherduck": "b694aa93969368381cb9d9bd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b694aa93969368381cb9d9bd", "postgresql": "b694aa93969368381cb9d9bd", "snowflake": "58d5f123164e58c358315897", -"tsql": "b694aa93969368381cb9d9bd" +"tsql": "fdd892c7bbc36567f9619175" } ], [ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -4343,8 +4343,8 @@ "databricks": "a49cf160806465e17f3bb015", "duckdb": "2a0e1cbf59d282b31816d003", "generic": "2a0e1cbf59d282b31816d003", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a49cf160806465e17f3bb015", +"motherduck": "2a0e1cbf59d282b31816d003", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2a0e1cbf59d282b31816d003", "postgresql": "2a0e1cbf59d282b31816d003", "snowflake": "8cdd1c724939d35c854480a1", @@ -4360,8 +4360,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -4373,12 +4373,12 @@ { "DuckDB": "401e8475dd39d667a86332fd", "None": "401e8475dd39d667a86332fd", -"bigquery": "122a82cc999a6e522d790b87", +"bigquery": "0439c1de159d15c43453a07b", "databricks": "e33f1ebea03ff1cb2c6e78c4", "duckdb": "401e8475dd39d667a86332fd", "generic": "401e8475dd39d667a86332fd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "401e8475dd39d667a86332fd", +"motherduck": "401e8475dd39d667a86332fd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "401e8475dd39d667a86332fd", "postgresql": "401e8475dd39d667a86332fd", "snowflake": "5f8d9b6f22793dc26cf2fd62", @@ -4394,8 +4394,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -4411,8 +4411,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -4428,8 +4428,8 @@ "databricks": "87fed6659d6324c5ef0c6ee2", "duckdb": "c344d36a6751b773df74ee3c", "generic": "87fed6659d6324c5ef0c6ee2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "87fed6659d6324c5ef0c6ee2", +"motherduck": "c344d36a6751b773df74ee3c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "87fed6659d6324c5ef0c6ee2", "postgresql": "87fed6659d6324c5ef0c6ee2", "snowflake": "87fed6659d6324c5ef0c6ee2", @@ -4441,14 +4441,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -4457,17 +4457,17 @@ "type", { "DuckDB": "3147a9e06daa6de4a4df5abc", -"None": "3147a9e06daa6de4a4df5abc", -"bigquery": "f276e907770196606577c932", -"databricks": "3147a9e06daa6de4a4df5abc", +"None": "da0625706f12b764a6912278", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "da0625706f12b764a6912278", "duckdb": "3147a9e06daa6de4a4df5abc", -"generic": "3147a9e06daa6de4a4df5abc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3147a9e06daa6de4a4df5abc", +"generic": "da0625706f12b764a6912278", +"motherduck": "3147a9e06daa6de4a4df5abc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3147a9e06daa6de4a4df5abc", "postgresql": "3147a9e06daa6de4a4df5abc", "snowflake": "67b5874a2c569226c490211f", -"tsql": "3147a9e06daa6de4a4df5abc" +"tsql": "da0625706f12b764a6912278" } ], [ @@ -4479,8 +4479,8 @@ "databricks": "b2598e21f41a4625fa001e55", "duckdb": "b2598e21f41a4625fa001e55", "generic": "b2598e21f41a4625fa001e55", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b2598e21f41a4625fa001e55", +"motherduck": "b2598e21f41a4625fa001e55", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b2598e21f41a4625fa001e55", "postgresql": "b2598e21f41a4625fa001e55", "snowflake": "b2598e21f41a4625fa001e55", @@ -4496,8 +4496,8 @@ "databricks": "e62af34c2d31bc9ab880629e", "duckdb": "8a7aa7fa75696327320e0add", "generic": "8a7aa7fa75696327320e0add", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e62af34c2d31bc9ab880629e", +"motherduck": "8a7aa7fa75696327320e0add", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a7aa7fa75696327320e0add", "postgresql": "8a7aa7fa75696327320e0add", "snowflake": "8a7aa7fa75696327320e0add", @@ -4513,8 +4513,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -4526,12 +4526,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -4547,8 +4547,8 @@ "databricks": "f7fca17b74cc50ae3de8c826", "duckdb": "f7fca17b74cc50ae3de8c826", "generic": "f7fca17b74cc50ae3de8c826", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f7fca17b74cc50ae3de8c826", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f7fca17b74cc50ae3de8c826", "postgresql": "f7fca17b74cc50ae3de8c826", "snowflake": "55155766823b6a3a5e882fc2", @@ -4560,14 +4560,14 @@ { "DuckDB": "95050a5e73c2bff179a785b7", "None": "95050a5e73c2bff179a785b7", -"bigquery": "95050a5e73c2bff179a785b7", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "95050a5e73c2bff179a785b7", "duckdb": "95050a5e73c2bff179a785b7", "generic": "95050a5e73c2bff179a785b7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "95050a5e73c2bff179a785b7", -"postgres": "95050a5e73c2bff179a785b7", -"postgresql": "95050a5e73c2bff179a785b7", +"motherduck": "95050a5e73c2bff179a785b7", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e197cb69fc5e5754a25d4038", +"postgresql": "e197cb69fc5e5754a25d4038", "snowflake": "95050a5e73c2bff179a785b7", "tsql": "95050a5e73c2bff179a785b7" } @@ -4581,8 +4581,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -4594,14 +4594,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -4610,17 +4610,17 @@ "type", { "DuckDB": "314ed506f24e92300a425780", -"None": "314ed506f24e92300a425780", -"bigquery": "314ed506f24e92300a425780", -"databricks": "314ed506f24e92300a425780", +"None": "9a66317fb8d040bf991502c0", +"bigquery": "9bafb08d41fd1c208b5deca4", +"databricks": "9a66317fb8d040bf991502c0", "duckdb": "314ed506f24e92300a425780", -"generic": "314ed506f24e92300a425780", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "314ed506f24e92300a425780", +"generic": "9a66317fb8d040bf991502c0", +"motherduck": "314ed506f24e92300a425780", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "314ed506f24e92300a425780", "postgresql": "314ed506f24e92300a425780", "snowflake": "314ed506f24e92300a425780", -"tsql": "314ed506f24e92300a425780" +"tsql": "9a66317fb8d040bf991502c0" } ], [ @@ -4632,8 +4632,8 @@ "databricks": "f4c43a87a78970bca1f88b09", "duckdb": "f4c43a87a78970bca1f88b09", "generic": "f4c43a87a78970bca1f88b09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4c43a87a78970bca1f88b09", +"motherduck": "f4c43a87a78970bca1f88b09", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c43a87a78970bca1f88b09", "postgresql": "f4c43a87a78970bca1f88b09", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -4649,10 +4649,10 @@ "databricks": "f8d53ecf4ba59d519bb15d38", "duckdb": "f8d53ecf4ba59d519bb15d38", "generic": "f8d53ecf4ba59d519bb15d38", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f8d53ecf4ba59d519bb15d38", -"postgres": "f8d53ecf4ba59d519bb15d38", -"postgresql": "f8d53ecf4ba59d519bb15d38", +"motherduck": "f8d53ecf4ba59d519bb15d38", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "61d79925ac143bbc1051c8fa", +"postgresql": "61d79925ac143bbc1051c8fa", "snowflake": "f8d53ecf4ba59d519bb15d38", "tsql": "f8d53ecf4ba59d519bb15d38" } @@ -4666,8 +4666,8 @@ "databricks": "d6e37cd37419bfb26e09b080", "duckdb": "d6e37cd37419bfb26e09b080", "generic": "d6e37cd37419bfb26e09b080", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d6e37cd37419bfb26e09b080", +"motherduck": "d6e37cd37419bfb26e09b080", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d6e37cd37419bfb26e09b080", "postgresql": "d6e37cd37419bfb26e09b080", "snowflake": "d6e37cd37419bfb26e09b080", @@ -4683,8 +4683,8 @@ "databricks": "94f6df33eccb97170eccb01b", "duckdb": "94f6df33eccb97170eccb01b", "generic": "94f6df33eccb97170eccb01b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "94f6df33eccb97170eccb01b", +"motherduck": "94f6df33eccb97170eccb01b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "94f6df33eccb97170eccb01b", "postgresql": "94f6df33eccb97170eccb01b", "snowflake": "94f6df33eccb97170eccb01b", @@ -4700,8 +4700,8 @@ "databricks": "8c0b6775b8364e21d86e6e85", "duckdb": "8c0b6775b8364e21d86e6e85", "generic": "8c0b6775b8364e21d86e6e85", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8c0b6775b8364e21d86e6e85", +"motherduck": "8c0b6775b8364e21d86e6e85", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8c0b6775b8364e21d86e6e85", "postgresql": "8c0b6775b8364e21d86e6e85", "snowflake": "8c0b6775b8364e21d86e6e85", @@ -4717,8 +4717,8 @@ "databricks": "2038903a8b2174e3b3525f45", "duckdb": "2038903a8b2174e3b3525f45", "generic": "2038903a8b2174e3b3525f45", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2038903a8b2174e3b3525f45", +"motherduck": "2038903a8b2174e3b3525f45", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2038903a8b2174e3b3525f45", "postgresql": "2038903a8b2174e3b3525f45", "snowflake": "2038903a8b2174e3b3525f45", @@ -4728,18 +4728,18 @@ [ "type", { -"DuckDB": "57a219193ee01aa40a7a339d", -"None": "1cff449f8adc98f1234ffeec", -"bigquery": "c49aa38b33ba6204f42571c8", -"databricks": "1cff449f8adc98f1234ffeec", -"duckdb": "57a219193ee01aa40a7a339d", -"generic": "1cff449f8adc98f1234ffeec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1cff449f8adc98f1234ffeec", -"postgres": "1cff449f8adc98f1234ffeec", -"postgresql": "1cff449f8adc98f1234ffeec", +"DuckDB": "bfb659237d70eae4d07a6d10", +"None": "009acb58d411c47680971462", +"bigquery": "c74425c5ce650ac69c83ca5b", +"databricks": "009acb58d411c47680971462", +"duckdb": "bfb659237d70eae4d07a6d10", +"generic": "009acb58d411c47680971462", +"motherduck": "bfb659237d70eae4d07a6d10", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bfb659237d70eae4d07a6d10", +"postgresql": "bfb659237d70eae4d07a6d10", "snowflake": "bfb659237d70eae4d07a6d10", -"tsql": "1cff449f8adc98f1234ffeec" +"tsql": "009acb58d411c47680971462" } ], [ @@ -4751,8 +4751,8 @@ "databricks": "e0faa139ea3b65b1356bd333", "duckdb": "e0faa139ea3b65b1356bd333", "generic": "e0faa139ea3b65b1356bd333", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e0faa139ea3b65b1356bd333", +"motherduck": "e0faa139ea3b65b1356bd333", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e0faa139ea3b65b1356bd333", "postgresql": "e0faa139ea3b65b1356bd333", "snowflake": "e0faa139ea3b65b1356bd333", @@ -4768,10 +4768,10 @@ "databricks": "a3acd7ba35e81a800f967bd3", "duckdb": "42d4a23d9a52e79d0c3458d3", "generic": "a3acd7ba35e81a800f967bd3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a3acd7ba35e81a800f967bd3", -"postgres": "a3acd7ba35e81a800f967bd3", -"postgresql": "a3acd7ba35e81a800f967bd3", +"motherduck": "42d4a23d9a52e79d0c3458d3", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "42d4a23d9a52e79d0c3458d3", +"postgresql": "42d4a23d9a52e79d0c3458d3", "snowflake": "a3acd7ba35e81a800f967bd3", "tsql": "a3acd7ba35e81a800f967bd3" } @@ -4785,8 +4785,8 @@ "databricks": "2795ccedbfd3fa0bed5fdfcd", "duckdb": "2795ccedbfd3fa0bed5fdfcd", "generic": "2795ccedbfd3fa0bed5fdfcd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2795ccedbfd3fa0bed5fdfcd", +"motherduck": "2795ccedbfd3fa0bed5fdfcd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2795ccedbfd3fa0bed5fdfcd", "postgresql": "2795ccedbfd3fa0bed5fdfcd", "snowflake": "2795ccedbfd3fa0bed5fdfcd", @@ -4802,10 +4802,10 @@ "databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -4819,8 +4819,8 @@ "databricks": "4a014523269fae92f3ad421a", "duckdb": "4a014523269fae92f3ad421a", "generic": "4a014523269fae92f3ad421a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4a014523269fae92f3ad421a", +"motherduck": "4a014523269fae92f3ad421a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4a014523269fae92f3ad421a", "postgresql": "4a014523269fae92f3ad421a", "snowflake": "4a014523269fae92f3ad421a", @@ -4836,8 +4836,8 @@ "databricks": "684469f7d4198e43e63072ce", "duckdb": "684469f7d4198e43e63072ce", "generic": "684469f7d4198e43e63072ce", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "684469f7d4198e43e63072ce", +"motherduck": "684469f7d4198e43e63072ce", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "684469f7d4198e43e63072ce", "postgresql": "684469f7d4198e43e63072ce", "snowflake": "684469f7d4198e43e63072ce", @@ -4853,8 +4853,8 @@ "databricks": "e998cd8f0f74af0526b75ff0", "duckdb": "e998cd8f0f74af0526b75ff0", "generic": "e998cd8f0f74af0526b75ff0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e998cd8f0f74af0526b75ff0", +"motherduck": "e998cd8f0f74af0526b75ff0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -4870,8 +4870,8 @@ "databricks": "1a804f317a3e9a0016925b04", "duckdb": "110327dc45eac0e8d54a96dc", "generic": "110327dc45eac0e8d54a96dc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1a804f317a3e9a0016925b04", +"motherduck": "110327dc45eac0e8d54a96dc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "110327dc45eac0e8d54a96dc", "postgresql": "110327dc45eac0e8d54a96dc", "snowflake": "110327dc45eac0e8d54a96dc", @@ -4882,34 +4882,34 @@ "type", { "DuckDB": "5abf4ec799a2ec32fb10da7e", -"None": "5abf4ec799a2ec32fb10da7e", -"bigquery": "5abf4ec799a2ec32fb10da7e", -"databricks": "5abf4ec799a2ec32fb10da7e", +"None": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", +"databricks": "4b356542044f1267fcfc487a", "duckdb": "5abf4ec799a2ec32fb10da7e", -"generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", -"postgres": "5abf4ec799a2ec32fb10da7e", -"postgresql": "5abf4ec799a2ec32fb10da7e", +"generic": "4b356542044f1267fcfc487a", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "a5656d70e208254d1d3e9b4f", -"tsql": "5abf4ec799a2ec32fb10da7e" +"tsql": "4b356542044f1267fcfc487a" } ], [ "type", { "DuckDB": "f7fca17b74cc50ae3de8c826", -"None": "f7fca17b74cc50ae3de8c826", -"bigquery": "f7fca17b74cc50ae3de8c826", -"databricks": "f7fca17b74cc50ae3de8c826", +"None": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", +"databricks": "d2c97d221599d038d9875873", "duckdb": "f7fca17b74cc50ae3de8c826", -"generic": "f7fca17b74cc50ae3de8c826", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f7fca17b74cc50ae3de8c826", -"postgres": "f7fca17b74cc50ae3de8c826", -"postgresql": "f7fca17b74cc50ae3de8c826", +"generic": "d2c97d221599d038d9875873", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "55155766823b6a3a5e882fc2", -"tsql": "f7fca17b74cc50ae3de8c826" +"tsql": "d2c97d221599d038d9875873" } ], [ @@ -4917,12 +4917,12 @@ { "DuckDB": "4d02ddd0ab80d0d46bf48a5f", "None": "4d02ddd0ab80d0d46bf48a5f", -"bigquery": "4d02ddd0ab80d0d46bf48a5f", +"bigquery": "3d7437d0bdf5ed3aa20531f2", "databricks": "4d02ddd0ab80d0d46bf48a5f", "duckdb": "4d02ddd0ab80d0d46bf48a5f", "generic": "4d02ddd0ab80d0d46bf48a5f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4d02ddd0ab80d0d46bf48a5f", +"motherduck": "4d02ddd0ab80d0d46bf48a5f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4d02ddd0ab80d0d46bf48a5f", "postgresql": "4d02ddd0ab80d0d46bf48a5f", "snowflake": "4d02ddd0ab80d0d46bf48a5f", @@ -4934,14 +4934,14 @@ { "DuckDB": "18db5afe2c1e80d1ee652b54", "None": "18db5afe2c1e80d1ee652b54", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", -"postgres": "18db5afe2c1e80d1ee652b54", -"postgresql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "47d64a5994d42a2b577166ea", +"postgresql": "47d64a5994d42a2b577166ea", "snowflake": "18db5afe2c1e80d1ee652b54", "tsql": "18db5afe2c1e80d1ee652b54" } @@ -4955,8 +4955,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -4972,8 +4972,8 @@ "databricks": "d5af64f521c46f0f5c6d1ad0", "duckdb": "d5af64f521c46f0f5c6d1ad0", "generic": "d5af64f521c46f0f5c6d1ad0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d5af64f521c46f0f5c6d1ad0", +"motherduck": "d5af64f521c46f0f5c6d1ad0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d5af64f521c46f0f5c6d1ad0", "postgresql": "d5af64f521c46f0f5c6d1ad0", "snowflake": "d5af64f521c46f0f5c6d1ad0", @@ -4989,8 +4989,8 @@ "databricks": "6e4f718d80b641c205a88635", "duckdb": "38e28682c0f47e13e642ab1c", "generic": "38e28682c0f47e13e642ab1c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "25f9faed41d5d23ca3fa8175", +"motherduck": "38e28682c0f47e13e642ab1c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "38e28682c0f47e13e642ab1c", "postgresql": "38e28682c0f47e13e642ab1c", "snowflake": "41cf14d12db9a99ffd9a4514", @@ -5006,8 +5006,8 @@ "databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "0a735e1254c48785122ee5a3", "generic": "558da38ec4d9d5d6c72f60cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "558da38ec4d9d5d6c72f60cf", +"motherduck": "0a735e1254c48785122ee5a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "558da38ec4d9d5d6c72f60cf", "postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "558da38ec4d9d5d6c72f60cf", @@ -5023,8 +5023,8 @@ "databricks": "c16b4b4f382469fb186a9022", "duckdb": "c16b4b4f382469fb186a9022", "generic": "c16b4b4f382469fb186a9022", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c16b4b4f382469fb186a9022", +"motherduck": "c16b4b4f382469fb186a9022", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c16b4b4f382469fb186a9022", "postgresql": "c16b4b4f382469fb186a9022", "snowflake": "c16b4b4f382469fb186a9022", @@ -5036,14 +5036,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -5057,8 +5057,8 @@ "databricks": "9e4a50a8aab648a9737f7d0c", "duckdb": "9e4a50a8aab648a9737f7d0c", "generic": "9e4a50a8aab648a9737f7d0c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9e4a50a8aab648a9737f7d0c", +"motherduck": "9e4a50a8aab648a9737f7d0c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "36d29258901a4d8c296baa68", "postgresql": "36d29258901a4d8c296baa68", "snowflake": "9e4a50a8aab648a9737f7d0c", @@ -5074,8 +5074,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -5091,8 +5091,8 @@ "databricks": "e8605878c6fffc044556a858", "duckdb": "e8605878c6fffc044556a858", "generic": "e8605878c6fffc044556a858", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8605878c6fffc044556a858", +"motherduck": "e8605878c6fffc044556a858", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8605878c6fffc044556a858", "postgresql": "e8605878c6fffc044556a858", "snowflake": "e8605878c6fffc044556a858", @@ -5108,8 +5108,8 @@ "databricks": "e8ef65ce7616ceeafffe41f6", "duckdb": "e8ef65ce7616ceeafffe41f6", "generic": "e8ef65ce7616ceeafffe41f6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8ef65ce7616ceeafffe41f6", +"motherduck": "e8ef65ce7616ceeafffe41f6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8ef65ce7616ceeafffe41f6", "postgresql": "e8ef65ce7616ceeafffe41f6", "snowflake": "e8ef65ce7616ceeafffe41f6", @@ -5125,8 +5125,8 @@ "databricks": "77ffdb80029bd6e2cca768ad", "duckdb": "77ffdb80029bd6e2cca768ad", "generic": "77ffdb80029bd6e2cca768ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "77ffdb80029bd6e2cca768ad", +"motherduck": "77ffdb80029bd6e2cca768ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "77ffdb80029bd6e2cca768ad", "postgresql": "77ffdb80029bd6e2cca768ad", "snowflake": "77ffdb80029bd6e2cca768ad", @@ -5142,8 +5142,8 @@ "databricks": "c4346d0257336219f1d26890", "duckdb": "c4346d0257336219f1d26890", "generic": "c4346d0257336219f1d26890", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c4346d0257336219f1d26890", +"motherduck": "c4346d0257336219f1d26890", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c4346d0257336219f1d26890", "postgresql": "c4346d0257336219f1d26890", "snowflake": "4e24a80b20b9dee0cbbfac17", @@ -5154,17 +5154,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -5172,12 +5172,12 @@ { "DuckDB": "3e611643f0beab4953c4e09b", "None": "3e611643f0beab4953c4e09b", -"bigquery": "2a0918be53051fe629d9ff8f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "3e611643f0beab4953c4e09b", "duckdb": "3e611643f0beab4953c4e09b", "generic": "3e611643f0beab4953c4e09b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3e611643f0beab4953c4e09b", +"motherduck": "3e611643f0beab4953c4e09b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3e611643f0beab4953c4e09b", "postgresql": "3e611643f0beab4953c4e09b", "snowflake": "3e611643f0beab4953c4e09b", @@ -5189,14 +5189,14 @@ { "DuckDB": "776e4bf23c8ef10ffaceade2", "None": "776e4bf23c8ef10ffaceade2", -"bigquery": "776e4bf23c8ef10ffaceade2", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "776e4bf23c8ef10ffaceade2", "duckdb": "776e4bf23c8ef10ffaceade2", "generic": "776e4bf23c8ef10ffaceade2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "776e4bf23c8ef10ffaceade2", -"postgres": "776e4bf23c8ef10ffaceade2", -"postgresql": "776e4bf23c8ef10ffaceade2", +"motherduck": "776e4bf23c8ef10ffaceade2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5d06b10843e3e6487fb9906a", +"postgresql": "5d06b10843e3e6487fb9906a", "snowflake": "776e4bf23c8ef10ffaceade2", "tsql": "776e4bf23c8ef10ffaceade2" } @@ -5210,8 +5210,8 @@ "databricks": "3915f3f73328f0838497b2d5", "duckdb": "3915f3f73328f0838497b2d5", "generic": "3915f3f73328f0838497b2d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3915f3f73328f0838497b2d5", +"motherduck": "3915f3f73328f0838497b2d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3915f3f73328f0838497b2d5", "postgresql": "3915f3f73328f0838497b2d5", "snowflake": "f43dac38f34a1c0618a3f812", @@ -5227,8 +5227,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -5244,8 +5244,8 @@ "databricks": "eed0645c1e86fa1a6e35ccaa", "duckdb": "eed0645c1e86fa1a6e35ccaa", "generic": "eed0645c1e86fa1a6e35ccaa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eed0645c1e86fa1a6e35ccaa", +"motherduck": "eed0645c1e86fa1a6e35ccaa", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eed0645c1e86fa1a6e35ccaa", "postgresql": "eed0645c1e86fa1a6e35ccaa", "snowflake": "97a20344d811fb77c7690863", @@ -5256,17 +5256,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -5278,8 +5278,8 @@ "databricks": "962ea05bab624c1f8c259a65", "duckdb": "962ea05bab624c1f8c259a65", "generic": "962ea05bab624c1f8c259a65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "962ea05bab624c1f8c259a65", +"motherduck": "962ea05bab624c1f8c259a65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "962ea05bab624c1f8c259a65", "postgresql": "962ea05bab624c1f8c259a65", "snowflake": "962ea05bab624c1f8c259a65", @@ -5295,8 +5295,8 @@ "databricks": "d638c2852078bf3facb9dc9e", "duckdb": "207acc46dcc150502155f4a3", "generic": "d638c2852078bf3facb9dc9e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "207acc46dcc150502155f4a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6c49aec265f259dd95b5310e", "postgresql": "6c49aec265f259dd95b5310e", "snowflake": "ef2c05747b795f92c0b523ee", @@ -5312,10 +5312,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "9d006c5299feb04426ce31ac", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "9d006c5299feb04426ce31ac", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -5329,8 +5329,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -5346,8 +5346,8 @@ "databricks": "6a726882dbf443e39240fc33", "duckdb": "6a726882dbf443e39240fc33", "generic": "6a726882dbf443e39240fc33", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6a726882dbf443e39240fc33", +"motherduck": "6a726882dbf443e39240fc33", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6a726882dbf443e39240fc33", "postgresql": "6a726882dbf443e39240fc33", "snowflake": "6a726882dbf443e39240fc33", @@ -5363,8 +5363,8 @@ "databricks": "c4860fb394ecca5e4515f1c9", "duckdb": "c4860fb394ecca5e4515f1c9", "generic": "c4860fb394ecca5e4515f1c9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c4860fb394ecca5e4515f1c9", +"motherduck": "c4860fb394ecca5e4515f1c9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c4860fb394ecca5e4515f1c9", "postgresql": "c4860fb394ecca5e4515f1c9", "snowflake": "c4860fb394ecca5e4515f1c9", @@ -5375,17 +5375,17 @@ "type", { "DuckDB": "363159f251e264604338e690", -"None": "363159f251e264604338e690", -"bigquery": "363159f251e264604338e690", -"databricks": "363159f251e264604338e690", +"None": "9a63ad6168418af23d9b6fd1", +"bigquery": "8b8d5f75ba6dec68b4cebc6b", +"databricks": "9a63ad6168418af23d9b6fd1", "duckdb": "363159f251e264604338e690", -"generic": "363159f251e264604338e690", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "363159f251e264604338e690", -"postgres": "363159f251e264604338e690", -"postgresql": "363159f251e264604338e690", +"generic": "9a63ad6168418af23d9b6fd1", +"motherduck": "363159f251e264604338e690", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f276e907770196606577c932", +"postgresql": "f276e907770196606577c932", "snowflake": "363159f251e264604338e690", -"tsql": "363159f251e264604338e690" +"tsql": "9a63ad6168418af23d9b6fd1" } ], [ @@ -5397,8 +5397,8 @@ "databricks": "fe1d4d12e4b12d78f4ce545d", "duckdb": "fe1d4d12e4b12d78f4ce545d", "generic": "fe1d4d12e4b12d78f4ce545d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe1d4d12e4b12d78f4ce545d", +"motherduck": "fe1d4d12e4b12d78f4ce545d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fe1d4d12e4b12d78f4ce545d", "postgresql": "fe1d4d12e4b12d78f4ce545d", "snowflake": "fe1d4d12e4b12d78f4ce545d", @@ -5414,10 +5414,10 @@ "databricks": "5829c1427d562e21c1ed4dfe", "duckdb": "5829c1427d562e21c1ed4dfe", "generic": "5829c1427d562e21c1ed4dfe", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5829c1427d562e21c1ed4dfe", -"postgres": "5829c1427d562e21c1ed4dfe", -"postgresql": "5829c1427d562e21c1ed4dfe", +"motherduck": "5829c1427d562e21c1ed4dfe", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "348b6977d03b636ecd10e63d", +"postgresql": "348b6977d03b636ecd10e63d", "snowflake": "5829c1427d562e21c1ed4dfe", "tsql": "5829c1427d562e21c1ed4dfe" } @@ -5431,8 +5431,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -5443,15 +5443,15 @@ "type", { "DuckDB": "e3ba57c3380e7ca53675e905", -"None": "85dad253f82f475892a7b35b", -"bigquery": "85dad253f82f475892a7b35b", -"databricks": "534433bc99c8d49f592ba435", +"None": "87673dc143bcd33d8688dc34", +"bigquery": "87673dc143bcd33d8688dc34", +"databricks": "87673dc143bcd33d8688dc34", "duckdb": "e3ba57c3380e7ca53675e905", -"generic": "85dad253f82f475892a7b35b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dd1a4411040373d9cd82f86", -"postgres": "463c3432687f6ea95aa60355", -"postgresql": "463c3432687f6ea95aa60355", +"generic": "87673dc143bcd33d8688dc34", +"motherduck": "e3ba57c3380e7ca53675e905", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "87673dc143bcd33d8688dc34", +"postgresql": "87673dc143bcd33d8688dc34", "snowflake": "0161e12cd4d2efd1b5557348", "tsql": "87673dc143bcd33d8688dc34" } @@ -5460,17 +5460,17 @@ "type", { "DuckDB": "1fe8a2d5a41383bd8e502808", -"None": "1fe8a2d5a41383bd8e502808", +"None": "70f3d045595fbd43219efb36", "bigquery": "70f3d045595fbd43219efb36", "databricks": "70f3d045595fbd43219efb36", "duckdb": "1fe8a2d5a41383bd8e502808", -"generic": "1fe8a2d5a41383bd8e502808", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70f3d045595fbd43219efb36", -"postgres": "1fe8a2d5a41383bd8e502808", -"postgresql": "1fe8a2d5a41383bd8e502808", +"generic": "70f3d045595fbd43219efb36", +"motherduck": "1fe8a2d5a41383bd8e502808", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3c17ab67cd8c034fc4911ef2", +"postgresql": "3c17ab67cd8c034fc4911ef2", "snowflake": "1fe8a2d5a41383bd8e502808", -"tsql": "1fe8a2d5a41383bd8e502808" +"tsql": "70f3d045595fbd43219efb36" } ], [ @@ -5478,14 +5478,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -5495,12 +5495,12 @@ { "DuckDB": "1c58068460e5c94bc2f83d53", "None": "1c58068460e5c94bc2f83d53", -"bigquery": "1c58068460e5c94bc2f83d53", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "1c58068460e5c94bc2f83d53", "duckdb": "1c58068460e5c94bc2f83d53", "generic": "1c58068460e5c94bc2f83d53", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1c58068460e5c94bc2f83d53", +"motherduck": "1c58068460e5c94bc2f83d53", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1c58068460e5c94bc2f83d53", "postgresql": "1c58068460e5c94bc2f83d53", "snowflake": "1c58068460e5c94bc2f83d53", @@ -5516,8 +5516,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -5533,8 +5533,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -5550,8 +5550,8 @@ "databricks": "26e762643db86e7646e57cba", "duckdb": "26e762643db86e7646e57cba", "generic": "26e762643db86e7646e57cba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "26e762643db86e7646e57cba", +"motherduck": "26e762643db86e7646e57cba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "26e762643db86e7646e57cba", "postgresql": "26e762643db86e7646e57cba", "snowflake": "26e762643db86e7646e57cba", @@ -5567,10 +5567,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -5584,8 +5584,8 @@ "databricks": "7c0baf18eb7c47a1c83b71c3", "duckdb": "243652b8dd9a0f3ac3501ed4", "generic": "f74ed75cd28860b40bc2c2e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "243652b8dd9a0f3ac3501ed4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "240398aee89da87a2f8274c9", "postgresql": "240398aee89da87a2f8274c9", "snowflake": "7422d281eba97abeec05a617", @@ -5601,8 +5601,8 @@ "databricks": "1bdf5908ca249b2331a0427c", "duckdb": "1bdf5908ca249b2331a0427c", "generic": "1bdf5908ca249b2331a0427c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1bdf5908ca249b2331a0427c", +"motherduck": "1bdf5908ca249b2331a0427c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1bdf5908ca249b2331a0427c", "postgresql": "1bdf5908ca249b2331a0427c", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -5618,8 +5618,8 @@ "databricks": "d725148d39793a0cbb9a6013", "duckdb": "d725148d39793a0cbb9a6013", "generic": "d725148d39793a0cbb9a6013", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d725148d39793a0cbb9a6013", +"motherduck": "d725148d39793a0cbb9a6013", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d725148d39793a0cbb9a6013", "postgresql": "d725148d39793a0cbb9a6013", "snowflake": "d725148d39793a0cbb9a6013", @@ -5635,8 +5635,8 @@ "databricks": "fe0bedb7dda0e9247a51fdee", "duckdb": "fe0bedb7dda0e9247a51fdee", "generic": "fe0bedb7dda0e9247a51fdee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe0bedb7dda0e9247a51fdee", +"motherduck": "fe0bedb7dda0e9247a51fdee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fe0bedb7dda0e9247a51fdee", "postgresql": "fe0bedb7dda0e9247a51fdee", "snowflake": "fe0bedb7dda0e9247a51fdee", @@ -5647,17 +5647,17 @@ "type", { "DuckDB": "2c44dc35fd1ef8701101802e", -"None": "2c44dc35fd1ef8701101802e", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "2c44dc35fd1ef8701101802e", +"None": "8441d00030df06ad6a5534d3", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "8441d00030df06ad6a5534d3", "duckdb": "2c44dc35fd1ef8701101802e", -"generic": "2c44dc35fd1ef8701101802e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c44dc35fd1ef8701101802e", -"postgres": "2c44dc35fd1ef8701101802e", -"postgresql": "2c44dc35fd1ef8701101802e", +"generic": "8441d00030df06ad6a5534d3", +"motherduck": "2c44dc35fd1ef8701101802e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8441d00030df06ad6a5534d3", +"postgresql": "8441d00030df06ad6a5534d3", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "2c44dc35fd1ef8701101802e" +"tsql": "8441d00030df06ad6a5534d3" } ], [ @@ -5665,14 +5665,14 @@ { "DuckDB": "c4346d0257336219f1d26890", "None": "c4346d0257336219f1d26890", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "c4346d0257336219f1d26890", "duckdb": "c4346d0257336219f1d26890", "generic": "c4346d0257336219f1d26890", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c4346d0257336219f1d26890", -"postgres": "c4346d0257336219f1d26890", -"postgresql": "c4346d0257336219f1d26890", +"motherduck": "c4346d0257336219f1d26890", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "19bdd92ea0cc5fb028657510", +"postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "4e24a80b20b9dee0cbbfac17", "tsql": "c4346d0257336219f1d26890" } @@ -5686,8 +5686,8 @@ "databricks": "73ed909ac0c80ba48ae6d17c", "duckdb": "73ed909ac0c80ba48ae6d17c", "generic": "73ed909ac0c80ba48ae6d17c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73ed909ac0c80ba48ae6d17c", +"motherduck": "73ed909ac0c80ba48ae6d17c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "73ed909ac0c80ba48ae6d17c", "postgresql": "73ed909ac0c80ba48ae6d17c", "snowflake": "73ed909ac0c80ba48ae6d17c", @@ -5703,8 +5703,8 @@ "databricks": "445eba49a8d5ccbd6775e1b6", "duckdb": "445eba49a8d5ccbd6775e1b6", "generic": "445eba49a8d5ccbd6775e1b6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "445eba49a8d5ccbd6775e1b6", +"motherduck": "445eba49a8d5ccbd6775e1b6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "445eba49a8d5ccbd6775e1b6", "postgresql": "445eba49a8d5ccbd6775e1b6", "snowflake": "445eba49a8d5ccbd6775e1b6", @@ -5716,14 +5716,14 @@ { "DuckDB": "097487e169d70127caa10537", "None": "097487e169d70127caa10537", -"bigquery": "097487e169d70127caa10537", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "097487e169d70127caa10537", "duckdb": "097487e169d70127caa10537", "generic": "097487e169d70127caa10537", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "097487e169d70127caa10537", -"postgres": "097487e169d70127caa10537", -"postgresql": "097487e169d70127caa10537", +"motherduck": "097487e169d70127caa10537", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ff580202d964731766475fad", +"postgresql": "ff580202d964731766475fad", "snowflake": "097487e169d70127caa10537", "tsql": "097487e169d70127caa10537" } @@ -5737,8 +5737,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -5750,12 +5750,12 @@ { "DuckDB": "383310937b9286f8d0206ea4", "None": "383310937b9286f8d0206ea4", -"bigquery": "383310937b9286f8d0206ea4", +"bigquery": "720295cb000a4e00c08800ef", "databricks": "383310937b9286f8d0206ea4", "duckdb": "383310937b9286f8d0206ea4", "generic": "383310937b9286f8d0206ea4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "383310937b9286f8d0206ea4", +"motherduck": "383310937b9286f8d0206ea4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "383310937b9286f8d0206ea4", "postgresql": "383310937b9286f8d0206ea4", "snowflake": "383310937b9286f8d0206ea4", @@ -5771,10 +5771,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "3efe8bb57c98fcbe74096b3c", "generic": "3efe8bb57c98fcbe74096b3c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1d8b945fd793ecdcacb90c21", -"postgres": "3efe8bb57c98fcbe74096b3c", -"postgresql": "3efe8bb57c98fcbe74096b3c", +"motherduck": "3efe8bb57c98fcbe74096b3c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "1d8b945fd793ecdcacb90c21", "tsql": "3efe8bb57c98fcbe74096b3c" } @@ -5788,8 +5788,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -5805,10 +5805,10 @@ "databricks": "bbb97828cd28c64282709796", "duckdb": "bbb97828cd28c64282709796", "generic": "bbb97828cd28c64282709796", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bbb97828cd28c64282709796", -"postgres": "bbb97828cd28c64282709796", -"postgresql": "bbb97828cd28c64282709796", +"motherduck": "bbb97828cd28c64282709796", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b210f8eb26c37b422f786c6b", +"postgresql": "b210f8eb26c37b422f786c6b", "snowflake": "bbb97828cd28c64282709796", "tsql": "bbb97828cd28c64282709796" } @@ -5822,8 +5822,8 @@ "databricks": "33edbb37fe70dd760e97e937", "duckdb": "33edbb37fe70dd760e97e937", "generic": "33edbb37fe70dd760e97e937", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "33edbb37fe70dd760e97e937", +"motherduck": "33edbb37fe70dd760e97e937", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "33edbb37fe70dd760e97e937", "postgresql": "33edbb37fe70dd760e97e937", "snowflake": "33edbb37fe70dd760e97e937", @@ -5839,8 +5839,8 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19a1a80ffc06169f32694de8", "postgresql": "19a1a80ffc06169f32694de8", "snowflake": "19a1a80ffc06169f32694de8", @@ -5856,8 +5856,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -5868,17 +5868,17 @@ "type", { "DuckDB": "d81d8c19074af0e39239164d", -"None": "1fec266134381c760a254d16", -"bigquery": "1fec266134381c760a254d16", -"databricks": "1fec266134381c760a254d16", +"None": "a2d22becd3f08974db215cfe", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "a2d22becd3f08974db215cfe", "duckdb": "d81d8c19074af0e39239164d", -"generic": "1fec266134381c760a254d16", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1fec266134381c760a254d16", -"postgres": "1fec266134381c760a254d16", -"postgresql": "1fec266134381c760a254d16", +"generic": "a2d22becd3f08974db215cfe", +"motherduck": "d81d8c19074af0e39239164d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a2d22becd3f08974db215cfe", +"postgresql": "a2d22becd3f08974db215cfe", "snowflake": "1fec266134381c760a254d16", -"tsql": "1fec266134381c760a254d16" +"tsql": "a2d22becd3f08974db215cfe" } ], [ @@ -5890,8 +5890,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -5907,8 +5907,8 @@ "databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "0a735e1254c48785122ee5a3", "generic": "558da38ec4d9d5d6c72f60cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "558da38ec4d9d5d6c72f60cf", +"motherduck": "0a735e1254c48785122ee5a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "558da38ec4d9d5d6c72f60cf", "postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "558da38ec4d9d5d6c72f60cf", @@ -5924,10 +5924,10 @@ "databricks": "38742651e9a5007a4262fae8", "duckdb": "38742651e9a5007a4262fae8", "generic": "38742651e9a5007a4262fae8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38742651e9a5007a4262fae8", -"postgres": "38742651e9a5007a4262fae8", -"postgresql": "38742651e9a5007a4262fae8", +"motherduck": "38742651e9a5007a4262fae8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c5bb8c8a6d4b467b51e54a73", +"postgresql": "c5bb8c8a6d4b467b51e54a73", "snowflake": "38742651e9a5007a4262fae8", "tsql": "38742651e9a5007a4262fae8" } @@ -5937,14 +5937,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -5952,14 +5952,14 @@ [ "type", { -"DuckDB": "cdf6c5188287878061490c2a", +"DuckDB": "67646dac6abfc002a1e7cacf", "None": "cdf6c5188287878061490c2a", "bigquery": "8b225d02320fa30e4a4e6cb1", "databricks": "f6e9bda6dd824d392effea47", -"duckdb": "cdf6c5188287878061490c2a", +"duckdb": "67646dac6abfc002a1e7cacf", "generic": "cdf6c5188287878061490c2a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5598594492668be60871af14", +"motherduck": "67646dac6abfc002a1e7cacf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cdf6c5188287878061490c2a", "postgresql": "cdf6c5188287878061490c2a", "snowflake": "5c643769adf301c8da08643f", @@ -5975,8 +5975,8 @@ "databricks": "e7455ff80a579a99488df79b", "duckdb": "e7455ff80a579a99488df79b", "generic": "e7455ff80a579a99488df79b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7455ff80a579a99488df79b", +"motherduck": "e7455ff80a579a99488df79b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7455ff80a579a99488df79b", "postgresql": "e7455ff80a579a99488df79b", "snowflake": "e7455ff80a579a99488df79b", @@ -5992,10 +5992,10 @@ "databricks": "2c6b371b030464f525066893", "duckdb": "2c6b371b030464f525066893", "generic": "2c6b371b030464f525066893", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c6b371b030464f525066893", -"postgres": "2c6b371b030464f525066893", -"postgresql": "2c6b371b030464f525066893", +"motherduck": "2c6b371b030464f525066893", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3feb479faeec777c2ade8afd", +"postgresql": "3feb479faeec777c2ade8afd", "snowflake": "2c6b371b030464f525066893", "tsql": "2c6b371b030464f525066893" } @@ -6005,14 +6005,14 @@ { "DuckDB": "881106a4a0d2de0ff7c56373", "None": "881106a4a0d2de0ff7c56373", -"bigquery": "881106a4a0d2de0ff7c56373", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "881106a4a0d2de0ff7c56373", "duckdb": "881106a4a0d2de0ff7c56373", "generic": "881106a4a0d2de0ff7c56373", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "881106a4a0d2de0ff7c56373", -"postgres": "881106a4a0d2de0ff7c56373", -"postgresql": "881106a4a0d2de0ff7c56373", +"motherduck": "881106a4a0d2de0ff7c56373", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cf167ec395fc843602e3b1e0", +"postgresql": "cf167ec395fc843602e3b1e0", "snowflake": "881106a4a0d2de0ff7c56373", "tsql": "881106a4a0d2de0ff7c56373" } @@ -6022,12 +6022,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -6039,14 +6039,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -6056,12 +6056,12 @@ { "DuckDB": "07aa3fe366bb8a3f270ea3d3", "None": "07aa3fe366bb8a3f270ea3d3", -"bigquery": "07aa3fe366bb8a3f270ea3d3", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "07aa3fe366bb8a3f270ea3d3", "duckdb": "07aa3fe366bb8a3f270ea3d3", "generic": "07aa3fe366bb8a3f270ea3d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "07aa3fe366bb8a3f270ea3d3", +"motherduck": "07aa3fe366bb8a3f270ea3d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "07aa3fe366bb8a3f270ea3d3", "postgresql": "07aa3fe366bb8a3f270ea3d3", "snowflake": "07aa3fe366bb8a3f270ea3d3", @@ -6077,8 +6077,8 @@ "databricks": "d47776cdb5b03cf4b1e7b63c", "duckdb": "d47776cdb5b03cf4b1e7b63c", "generic": "d47776cdb5b03cf4b1e7b63c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d47776cdb5b03cf4b1e7b63c", +"motherduck": "d47776cdb5b03cf4b1e7b63c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d47776cdb5b03cf4b1e7b63c", "postgresql": "d47776cdb5b03cf4b1e7b63c", "snowflake": "d47776cdb5b03cf4b1e7b63c", @@ -6094,8 +6094,8 @@ "databricks": "eed0645c1e86fa1a6e35ccaa", "duckdb": "eed0645c1e86fa1a6e35ccaa", "generic": "eed0645c1e86fa1a6e35ccaa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eed0645c1e86fa1a6e35ccaa", +"motherduck": "eed0645c1e86fa1a6e35ccaa", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eed0645c1e86fa1a6e35ccaa", "postgresql": "eed0645c1e86fa1a6e35ccaa", "snowflake": "97a20344d811fb77c7690863", @@ -6106,17 +6106,17 @@ "type", { "DuckDB": "a428302e4848ffe877284c84", -"None": "a428302e4848ffe877284c84", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "a428302e4848ffe877284c84", +"None": "c9b810639795a7bfab91b98d", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "c9b810639795a7bfab91b98d", "duckdb": "a428302e4848ffe877284c84", -"generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"generic": "c9b810639795a7bfab91b98d", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "a428302e4848ffe877284c84" +"tsql": "c9b810639795a7bfab91b98d" } ], [ @@ -6128,8 +6128,8 @@ "databricks": "3959952046272deb40567543", "duckdb": "3959952046272deb40567543", "generic": "3959952046272deb40567543", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3959952046272deb40567543", +"motherduck": "3959952046272deb40567543", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3959952046272deb40567543", "postgresql": "3959952046272deb40567543", "snowflake": "3959952046272deb40567543", @@ -6145,8 +6145,8 @@ "databricks": "c426d93641829fa3d8f49b8d", "duckdb": "c426d93641829fa3d8f49b8d", "generic": "c426d93641829fa3d8f49b8d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c426d93641829fa3d8f49b8d", +"motherduck": "c426d93641829fa3d8f49b8d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c426d93641829fa3d8f49b8d", "postgresql": "c426d93641829fa3d8f49b8d", "snowflake": "c426d93641829fa3d8f49b8d", @@ -6162,8 +6162,8 @@ "databricks": "da33de41a707d0e067e415fd", "duckdb": "da33de41a707d0e067e415fd", "generic": "da33de41a707d0e067e415fd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "da33de41a707d0e067e415fd", +"motherduck": "da33de41a707d0e067e415fd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "da33de41a707d0e067e415fd", "postgresql": "da33de41a707d0e067e415fd", "snowflake": "da33de41a707d0e067e415fd", @@ -6174,17 +6174,17 @@ "type", { "DuckDB": "3147a9e06daa6de4a4df5abc", -"None": "3147a9e06daa6de4a4df5abc", -"bigquery": "f276e907770196606577c932", -"databricks": "3147a9e06daa6de4a4df5abc", +"None": "da0625706f12b764a6912278", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "da0625706f12b764a6912278", "duckdb": "3147a9e06daa6de4a4df5abc", -"generic": "3147a9e06daa6de4a4df5abc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3147a9e06daa6de4a4df5abc", +"generic": "da0625706f12b764a6912278", +"motherduck": "3147a9e06daa6de4a4df5abc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3147a9e06daa6de4a4df5abc", "postgresql": "3147a9e06daa6de4a4df5abc", "snowflake": "67b5874a2c569226c490211f", -"tsql": "3147a9e06daa6de4a4df5abc" +"tsql": "da0625706f12b764a6912278" } ], [ @@ -6196,10 +6196,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -6213,8 +6213,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -6230,8 +6230,8 @@ "databricks": "be15dde1d6e0b4bb3f6da66f", "duckdb": "be15dde1d6e0b4bb3f6da66f", "generic": "be15dde1d6e0b4bb3f6da66f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "be15dde1d6e0b4bb3f6da66f", +"motherduck": "be15dde1d6e0b4bb3f6da66f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "be15dde1d6e0b4bb3f6da66f", "postgresql": "be15dde1d6e0b4bb3f6da66f", "snowflake": "be15dde1d6e0b4bb3f6da66f", @@ -6247,8 +6247,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -6260,14 +6260,14 @@ { "DuckDB": "73151c1398b30e5c5d18f4ce", "None": "73151c1398b30e5c5d18f4ce", -"bigquery": "73151c1398b30e5c5d18f4ce", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "73151c1398b30e5c5d18f4ce", "duckdb": "73151c1398b30e5c5d18f4ce", "generic": "73151c1398b30e5c5d18f4ce", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73151c1398b30e5c5d18f4ce", -"postgres": "73151c1398b30e5c5d18f4ce", -"postgresql": "73151c1398b30e5c5d18f4ce", +"motherduck": "73151c1398b30e5c5d18f4ce", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f68b7dbd74ec7c3b9f3066ef", +"postgresql": "f68b7dbd74ec7c3b9f3066ef", "snowflake": "73151c1398b30e5c5d18f4ce", "tsql": "73151c1398b30e5c5d18f4ce" } @@ -6278,11 +6278,11 @@ "DuckDB": "e3d9a113277b9c69432b32d1", "None": "e3d9a113277b9c69432b32d1", "bigquery": "e3d9a113277b9c69432b32d1", -"databricks": "4cda99191af9adfbe91e70f6", +"databricks": "e3d9a113277b9c69432b32d1", "duckdb": "e3d9a113277b9c69432b32d1", "generic": "e3d9a113277b9c69432b32d1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4cda99191af9adfbe91e70f6", +"motherduck": "e3d9a113277b9c69432b32d1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e3d9a113277b9c69432b32d1", "postgresql": "e3d9a113277b9c69432b32d1", "snowflake": "e3d9a113277b9c69432b32d1", @@ -6298,8 +6298,8 @@ "databricks": "b137c998bdaa2555d912e8fa", "duckdb": "27f973a6b2da27a79f5c0074", "generic": "b137c998bdaa2555d912e8fa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b137c998bdaa2555d912e8fa", +"motherduck": "27f973a6b2da27a79f5c0074", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b137c998bdaa2555d912e8fa", "postgresql": "b137c998bdaa2555d912e8fa", "snowflake": "b137c998bdaa2555d912e8fa", @@ -6315,8 +6315,8 @@ "databricks": "7a924f1a13854aa3260f3f57", "duckdb": "7a924f1a13854aa3260f3f57", "generic": "7a924f1a13854aa3260f3f57", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7a924f1a13854aa3260f3f57", +"motherduck": "7a924f1a13854aa3260f3f57", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7a924f1a13854aa3260f3f57", "postgresql": "7a924f1a13854aa3260f3f57", "snowflake": "7a924f1a13854aa3260f3f57", @@ -6332,8 +6332,8 @@ "databricks": "d90a78129f7050a540984f6f", "duckdb": "66f7ca02161349d637fa7d4e", "generic": "66f7ca02161349d637fa7d4e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d90a78129f7050a540984f6f", +"motherduck": "66f7ca02161349d637fa7d4e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66f7ca02161349d637fa7d4e", "postgresql": "66f7ca02161349d637fa7d4e", "snowflake": "66f7ca02161349d637fa7d4e", @@ -6349,8 +6349,8 @@ "databricks": "104d3e046c47032c37f947b8", "duckdb": "104d3e046c47032c37f947b8", "generic": "104d3e046c47032c37f947b8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "104d3e046c47032c37f947b8", +"motherduck": "104d3e046c47032c37f947b8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "104d3e046c47032c37f947b8", "postgresql": "104d3e046c47032c37f947b8", "snowflake": "104d3e046c47032c37f947b8", @@ -6366,8 +6366,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -6383,8 +6383,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -6400,8 +6400,8 @@ "databricks": "f3150219c38dba6b9f922447", "duckdb": "f3150219c38dba6b9f922447", "generic": "f3150219c38dba6b9f922447", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f3150219c38dba6b9f922447", +"motherduck": "f3150219c38dba6b9f922447", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f3150219c38dba6b9f922447", "postgresql": "f3150219c38dba6b9f922447", "snowflake": "f3150219c38dba6b9f922447", @@ -6417,8 +6417,8 @@ "databricks": "f6140be64a327a15b42b923d", "duckdb": "f6140be64a327a15b42b923d", "generic": "f6140be64a327a15b42b923d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f6140be64a327a15b42b923d", +"motherduck": "f6140be64a327a15b42b923d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f6140be64a327a15b42b923d", "postgresql": "f6140be64a327a15b42b923d", "snowflake": "f6140be64a327a15b42b923d", @@ -6430,14 +6430,14 @@ { "DuckDB": "e408f2d8251e1675179187e6", "None": "e408f2d8251e1675179187e6", -"bigquery": "e408f2d8251e1675179187e6", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "e408f2d8251e1675179187e6", "duckdb": "e408f2d8251e1675179187e6", "generic": "e408f2d8251e1675179187e6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e408f2d8251e1675179187e6", -"postgres": "e408f2d8251e1675179187e6", -"postgresql": "e408f2d8251e1675179187e6", +"motherduck": "e408f2d8251e1675179187e6", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "04bfa329b4dc3b5c43ad2560", +"postgresql": "04bfa329b4dc3b5c43ad2560", "snowflake": "e408f2d8251e1675179187e6", "tsql": "e408f2d8251e1675179187e6" } @@ -6451,8 +6451,8 @@ "databricks": "3959952046272deb40567543", "duckdb": "3959952046272deb40567543", "generic": "3959952046272deb40567543", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3959952046272deb40567543", +"motherduck": "3959952046272deb40567543", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3959952046272deb40567543", "postgresql": "3959952046272deb40567543", "snowflake": "3959952046272deb40567543", @@ -6468,8 +6468,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -6480,17 +6480,17 @@ "type", { "DuckDB": "087a9417cb511ff7bcfd6ccb", -"None": "383f8922f4b6f167d384037d", -"bigquery": "383f8922f4b6f167d384037d", -"databricks": "383f8922f4b6f167d384037d", +"None": "2e8f6dfec4597fb9373329e7", +"bigquery": "2e8f6dfec4597fb9373329e7", +"databricks": "2e8f6dfec4597fb9373329e7", "duckdb": "087a9417cb511ff7bcfd6ccb", -"generic": "383f8922f4b6f167d384037d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "383f8922f4b6f167d384037d", +"generic": "2e8f6dfec4597fb9373329e7", +"motherduck": "087a9417cb511ff7bcfd6ccb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "383f8922f4b6f167d384037d", "postgresql": "383f8922f4b6f167d384037d", "snowflake": "383f8922f4b6f167d384037d", -"tsql": "383f8922f4b6f167d384037d" +"tsql": "2e8f6dfec4597fb9373329e7" } ], [ @@ -6502,8 +6502,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -6519,8 +6519,8 @@ "databricks": "5d0a19a893dbf0c1e6e9f15c", "duckdb": "5d0a19a893dbf0c1e6e9f15c", "generic": "5d0a19a893dbf0c1e6e9f15c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d0a19a893dbf0c1e6e9f15c", +"motherduck": "5d0a19a893dbf0c1e6e9f15c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5d0a19a893dbf0c1e6e9f15c", "postgresql": "5d0a19a893dbf0c1e6e9f15c", "snowflake": "5d0a19a893dbf0c1e6e9f15c", @@ -6536,8 +6536,8 @@ "databricks": "2460e6e632163bbe7ecaf8a2", "duckdb": "2460e6e632163bbe7ecaf8a2", "generic": "2460e6e632163bbe7ecaf8a2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2460e6e632163bbe7ecaf8a2", +"motherduck": "2460e6e632163bbe7ecaf8a2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2460e6e632163bbe7ecaf8a2", "postgresql": "2460e6e632163bbe7ecaf8a2", "snowflake": "2460e6e632163bbe7ecaf8a2", @@ -6549,14 +6549,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -6570,8 +6570,8 @@ "databricks": "3b0d47536653277b7d98e328", "duckdb": "3b0d47536653277b7d98e328", "generic": "3b0d47536653277b7d98e328", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3b0d47536653277b7d98e328", +"motherduck": "3b0d47536653277b7d98e328", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3b0d47536653277b7d98e328", "postgresql": "3b0d47536653277b7d98e328", "snowflake": "3b0d47536653277b7d98e328", @@ -6587,8 +6587,8 @@ "databricks": "8352e528bde15549f0d0d870", "duckdb": "da08a000bb111759556a61cc", "generic": "df518e3c53cd9a166efacdb4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73477f3a6e7e1fd4288ea98f", +"motherduck": "da08a000bb111759556a61cc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "df518e3c53cd9a166efacdb4", "postgresql": "df518e3c53cd9a166efacdb4", "snowflake": "58581fd57ff3a9d0f74420dc", @@ -6601,11 +6601,11 @@ "DuckDB": "1f9deff638e476a7f168220b", "None": "1f9deff638e476a7f168220b", "bigquery": "1f9deff638e476a7f168220b", -"databricks": "eeac51d8e73fdd4f598fd1cd", +"databricks": "1f9deff638e476a7f168220b", "duckdb": "1f9deff638e476a7f168220b", "generic": "1f9deff638e476a7f168220b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eeac51d8e73fdd4f598fd1cd", +"motherduck": "1f9deff638e476a7f168220b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1f9deff638e476a7f168220b", "postgresql": "1f9deff638e476a7f168220b", "snowflake": "1f9deff638e476a7f168220b", @@ -6617,12 +6617,12 @@ { "DuckDB": "ebfad3a0b99f87bcac831ded", "None": "ebfad3a0b99f87bcac831ded", -"bigquery": "ebfad3a0b99f87bcac831ded", +"bigquery": "6da3227d362b48323df57544", "databricks": "ebfad3a0b99f87bcac831ded", "duckdb": "ebfad3a0b99f87bcac831ded", "generic": "ebfad3a0b99f87bcac831ded", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebfad3a0b99f87bcac831ded", +"motherduck": "ebfad3a0b99f87bcac831ded", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebfad3a0b99f87bcac831ded", "postgresql": "ebfad3a0b99f87bcac831ded", "snowflake": "ebfad3a0b99f87bcac831ded", @@ -6638,8 +6638,8 @@ "databricks": "4281b54de06f589b3507035a", "duckdb": "5620e893a908c6743b863bf6", "generic": "5620e893a908c6743b863bf6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4281b54de06f589b3507035a", +"motherduck": "5620e893a908c6743b863bf6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5620e893a908c6743b863bf6", "postgresql": "5620e893a908c6743b863bf6", "snowflake": "5620e893a908c6743b863bf6", @@ -6655,8 +6655,8 @@ "databricks": "48b9d23d0bb10c99b1325f6b", "duckdb": "48b9d23d0bb10c99b1325f6b", "generic": "48b9d23d0bb10c99b1325f6b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "48b9d23d0bb10c99b1325f6b", +"motherduck": "48b9d23d0bb10c99b1325f6b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "48b9d23d0bb10c99b1325f6b", "postgresql": "48b9d23d0bb10c99b1325f6b", "snowflake": "48b9d23d0bb10c99b1325f6b", @@ -6672,8 +6672,8 @@ "databricks": "8f0cf40e26c5ef0cbf1da302", "duckdb": "024a48135994929163b8f97b", "generic": "8f0cf40e26c5ef0cbf1da302", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "024a48135994929163b8f97b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "024a48135994929163b8f97b", "postgresql": "024a48135994929163b8f97b", "snowflake": "cc36c0fadc1a5838c69f0b9e", @@ -6685,14 +6685,14 @@ { "DuckDB": "4d0f0db1a550ed1e0da1a043", "None": "4d0f0db1a550ed1e0da1a043", -"bigquery": "4d0f0db1a550ed1e0da1a043", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "4d0f0db1a550ed1e0da1a043", "duckdb": "4d0f0db1a550ed1e0da1a043", "generic": "4d0f0db1a550ed1e0da1a043", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4d0f0db1a550ed1e0da1a043", -"postgres": "4d0f0db1a550ed1e0da1a043", -"postgresql": "4d0f0db1a550ed1e0da1a043", +"motherduck": "4d0f0db1a550ed1e0da1a043", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e47320d1030c166a05193bc4", +"postgresql": "e47320d1030c166a05193bc4", "snowflake": "4d0f0db1a550ed1e0da1a043", "tsql": "4d0f0db1a550ed1e0da1a043" } @@ -6706,8 +6706,8 @@ "databricks": "1275b5b0c52f69cf592c004b", "duckdb": "1275b5b0c52f69cf592c004b", "generic": "1275b5b0c52f69cf592c004b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1275b5b0c52f69cf592c004b", +"motherduck": "1275b5b0c52f69cf592c004b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1275b5b0c52f69cf592c004b", "postgresql": "1275b5b0c52f69cf592c004b", "snowflake": "1275b5b0c52f69cf592c004b", @@ -6723,8 +6723,8 @@ "databricks": "fb5907c56dd752ac5f1dbd95", "duckdb": "fb5907c56dd752ac5f1dbd95", "generic": "fb5907c56dd752ac5f1dbd95", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fb5907c56dd752ac5f1dbd95", +"motherduck": "fb5907c56dd752ac5f1dbd95", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fb5907c56dd752ac5f1dbd95", "postgresql": "fb5907c56dd752ac5f1dbd95", "snowflake": "fb5907c56dd752ac5f1dbd95", @@ -6740,8 +6740,8 @@ "databricks": "c82815abe30b3d099903f838", "duckdb": "c82815abe30b3d099903f838", "generic": "c82815abe30b3d099903f838", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c82815abe30b3d099903f838", +"motherduck": "c82815abe30b3d099903f838", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c82815abe30b3d099903f838", "postgresql": "c82815abe30b3d099903f838", "snowflake": "c82815abe30b3d099903f838", @@ -6757,8 +6757,8 @@ "databricks": "182a094bae8a9a33f67845ad", "duckdb": "182a094bae8a9a33f67845ad", "generic": "182a094bae8a9a33f67845ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "182a094bae8a9a33f67845ad", +"motherduck": "182a094bae8a9a33f67845ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "182a094bae8a9a33f67845ad", "postgresql": "182a094bae8a9a33f67845ad", "snowflake": "182a094bae8a9a33f67845ad", @@ -6774,8 +6774,8 @@ "databricks": "f4c43a87a78970bca1f88b09", "duckdb": "f4c43a87a78970bca1f88b09", "generic": "f4c43a87a78970bca1f88b09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4c43a87a78970bca1f88b09", +"motherduck": "f4c43a87a78970bca1f88b09", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c43a87a78970bca1f88b09", "postgresql": "f4c43a87a78970bca1f88b09", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -6791,8 +6791,8 @@ "databricks": "330fec4d6c49ac503e302b43", "duckdb": "330fec4d6c49ac503e302b43", "generic": "330fec4d6c49ac503e302b43", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "330fec4d6c49ac503e302b43", +"motherduck": "330fec4d6c49ac503e302b43", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f38ef32a483f1c4f89ae309e", "postgresql": "f38ef32a483f1c4f89ae309e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -6808,10 +6808,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -6821,12 +6821,12 @@ { "DuckDB": "8203bd5901a8ec71ff7c103e", "None": "8203bd5901a8ec71ff7c103e", -"bigquery": "2a0918be53051fe629d9ff8f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "8203bd5901a8ec71ff7c103e", "duckdb": "8203bd5901a8ec71ff7c103e", "generic": "8203bd5901a8ec71ff7c103e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8203bd5901a8ec71ff7c103e", +"motherduck": "8203bd5901a8ec71ff7c103e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8203bd5901a8ec71ff7c103e", "postgresql": "8203bd5901a8ec71ff7c103e", "snowflake": "8203bd5901a8ec71ff7c103e", @@ -6842,10 +6842,10 @@ "databricks": "9ff8ffdba7614214fe44bd7f", "duckdb": "9ff8ffdba7614214fe44bd7f", "generic": "9ff8ffdba7614214fe44bd7f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9ff8ffdba7614214fe44bd7f", -"postgres": "9ff8ffdba7614214fe44bd7f", -"postgresql": "9ff8ffdba7614214fe44bd7f", +"motherduck": "9ff8ffdba7614214fe44bd7f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "12a93f1979dd590479e084a8", +"postgresql": "12a93f1979dd590479e084a8", "snowflake": "9ff8ffdba7614214fe44bd7f", "tsql": "9ff8ffdba7614214fe44bd7f" } @@ -6859,10 +6859,10 @@ "databricks": "336fd1b7a1f7d8105c29280c", "duckdb": "0baaca265927140414f32b37", "generic": "336fd1b7a1f7d8105c29280c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "336fd1b7a1f7d8105c29280c", -"postgres": "336fd1b7a1f7d8105c29280c", -"postgresql": "336fd1b7a1f7d8105c29280c", +"motherduck": "0baaca265927140414f32b37", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9ea4932505663b43f527adad", +"postgresql": "9ea4932505663b43f527adad", "snowflake": "336fd1b7a1f7d8105c29280c", "tsql": "336fd1b7a1f7d8105c29280c" } @@ -6871,17 +6871,17 @@ "type", { "DuckDB": "fa45b7e4a9ef3b5a9c5781af", -"None": "fa45b7e4a9ef3b5a9c5781af", -"bigquery": "fa45b7e4a9ef3b5a9c5781af", -"databricks": "fa45b7e4a9ef3b5a9c5781af", +"None": "ebe2513b6cb4001e42cc464b", +"bigquery": "0c43dc837a50933667588ef1", +"databricks": "ebe2513b6cb4001e42cc464b", "duckdb": "fa45b7e4a9ef3b5a9c5781af", -"generic": "fa45b7e4a9ef3b5a9c5781af", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fa45b7e4a9ef3b5a9c5781af", +"generic": "ebe2513b6cb4001e42cc464b", +"motherduck": "fa45b7e4a9ef3b5a9c5781af", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fa45b7e4a9ef3b5a9c5781af", "postgresql": "fa45b7e4a9ef3b5a9c5781af", "snowflake": "fa45b7e4a9ef3b5a9c5781af", -"tsql": "fa45b7e4a9ef3b5a9c5781af" +"tsql": "ebe2513b6cb4001e42cc464b" } ], [ @@ -6893,8 +6893,8 @@ "databricks": "a22797fa0ef22aadba4ac3ec", "duckdb": "a22797fa0ef22aadba4ac3ec", "generic": "a22797fa0ef22aadba4ac3ec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a22797fa0ef22aadba4ac3ec", +"motherduck": "a22797fa0ef22aadba4ac3ec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a22797fa0ef22aadba4ac3ec", "postgresql": "a22797fa0ef22aadba4ac3ec", "snowflake": "a22797fa0ef22aadba4ac3ec", @@ -6906,14 +6906,14 @@ { "DuckDB": "881106a4a0d2de0ff7c56373", "None": "881106a4a0d2de0ff7c56373", -"bigquery": "881106a4a0d2de0ff7c56373", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "881106a4a0d2de0ff7c56373", "duckdb": "881106a4a0d2de0ff7c56373", "generic": "881106a4a0d2de0ff7c56373", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "881106a4a0d2de0ff7c56373", -"postgres": "881106a4a0d2de0ff7c56373", -"postgresql": "881106a4a0d2de0ff7c56373", +"motherduck": "881106a4a0d2de0ff7c56373", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cf167ec395fc843602e3b1e0", +"postgresql": "cf167ec395fc843602e3b1e0", "snowflake": "881106a4a0d2de0ff7c56373", "tsql": "881106a4a0d2de0ff7c56373" } @@ -6927,10 +6927,10 @@ "databricks": "2705f19942a4194a72576911", "duckdb": "2705f19942a4194a72576911", "generic": "2705f19942a4194a72576911", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2705f19942a4194a72576911", -"postgres": "2705f19942a4194a72576911", -"postgresql": "2705f19942a4194a72576911", +"motherduck": "2705f19942a4194a72576911", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5226d7e2a62c7650a87d6e84", +"postgresql": "5226d7e2a62c7650a87d6e84", "snowflake": "2705f19942a4194a72576911", "tsql": "2705f19942a4194a72576911" } @@ -6944,8 +6944,8 @@ "databricks": "79483dab720e706b74936744", "duckdb": "79483dab720e706b74936744", "generic": "79483dab720e706b74936744", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79483dab720e706b74936744", +"motherduck": "79483dab720e706b74936744", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79483dab720e706b74936744", "postgresql": "79483dab720e706b74936744", "snowflake": "79483dab720e706b74936744", @@ -6961,8 +6961,8 @@ "databricks": "7032aa8254308ba815883957", "duckdb": "7032aa8254308ba815883957", "generic": "7032aa8254308ba815883957", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7032aa8254308ba815883957", +"motherduck": "7032aa8254308ba815883957", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7032aa8254308ba815883957", "postgresql": "7032aa8254308ba815883957", "snowflake": "7032aa8254308ba815883957", @@ -6978,10 +6978,10 @@ "databricks": "6f763a99e9c69109526b40c0", "duckdb": "6f763a99e9c69109526b40c0", "generic": "6f763a99e9c69109526b40c0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6f763a99e9c69109526b40c0", -"postgres": "6f763a99e9c69109526b40c0", -"postgresql": "6f763a99e9c69109526b40c0", +"motherduck": "6f763a99e9c69109526b40c0", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5226d7e2a62c7650a87d6e84", +"postgresql": "5226d7e2a62c7650a87d6e84", "snowflake": "2705f19942a4194a72576911", "tsql": "6f763a99e9c69109526b40c0" } @@ -6990,17 +6990,17 @@ "type", { "DuckDB": "d9a22eb846c0561f4d74700b", -"None": "d9a22eb846c0561f4d74700b", -"bigquery": "d9a22eb846c0561f4d74700b", -"databricks": "d9a22eb846c0561f4d74700b", +"None": "93edc94c44e0732b1be558b1", +"bigquery": "93edc94c44e0732b1be558b1", +"databricks": "93edc94c44e0732b1be558b1", "duckdb": "d9a22eb846c0561f4d74700b", -"generic": "d9a22eb846c0561f4d74700b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d9a22eb846c0561f4d74700b", +"generic": "93edc94c44e0732b1be558b1", +"motherduck": "d9a22eb846c0561f4d74700b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d9a22eb846c0561f4d74700b", "postgresql": "d9a22eb846c0561f4d74700b", "snowflake": "9b5f44707b5f4764b82741be", -"tsql": "d9a22eb846c0561f4d74700b" +"tsql": "93edc94c44e0732b1be558b1" } ], [ @@ -7012,8 +7012,8 @@ "databricks": "ce3765fd18ee6aff1394ac23", "duckdb": "ce3765fd18ee6aff1394ac23", "generic": "ce3765fd18ee6aff1394ac23", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ce3765fd18ee6aff1394ac23", +"motherduck": "ce3765fd18ee6aff1394ac23", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ce3765fd18ee6aff1394ac23", "postgresql": "ce3765fd18ee6aff1394ac23", "snowflake": "ce3765fd18ee6aff1394ac23", @@ -7024,17 +7024,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -7046,8 +7046,8 @@ "databricks": "78b2cf275c640af54feeca31", "duckdb": "78b2cf275c640af54feeca31", "generic": "78b2cf275c640af54feeca31", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "78b2cf275c640af54feeca31", +"motherduck": "78b2cf275c640af54feeca31", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78b2cf275c640af54feeca31", "postgresql": "78b2cf275c640af54feeca31", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -7063,8 +7063,8 @@ "databricks": "87eaac3f093587d1d34762da", "duckdb": "87eaac3f093587d1d34762da", "generic": "87eaac3f093587d1d34762da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "87eaac3f093587d1d34762da", +"motherduck": "87eaac3f093587d1d34762da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "87eaac3f093587d1d34762da", "postgresql": "87eaac3f093587d1d34762da", "snowflake": "87eaac3f093587d1d34762da", @@ -7080,10 +7080,10 @@ "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", -"postgres": "5d25d5e13f81700741695ed4", -"postgresql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62131fb5b065f5de2e74a0f1", +"postgresql": "62131fb5b065f5de2e74a0f1", "snowflake": "5d25d5e13f81700741695ed4", "tsql": "5d25d5e13f81700741695ed4" } @@ -7097,10 +7097,10 @@ "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -7110,14 +7110,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -7131,8 +7131,8 @@ "databricks": "97cf3d84c55104df9751ec06", "duckdb": "97cf3d84c55104df9751ec06", "generic": "97cf3d84c55104df9751ec06", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "97cf3d84c55104df9751ec06", +"motherduck": "97cf3d84c55104df9751ec06", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "97cf3d84c55104df9751ec06", "postgresql": "97cf3d84c55104df9751ec06", "snowflake": "97cf3d84c55104df9751ec06", @@ -7148,8 +7148,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -7161,12 +7161,12 @@ { "DuckDB": "18db5afe2c1e80d1ee652b54", "None": "18db5afe2c1e80d1ee652b54", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "18db5afe2c1e80d1ee652b54", "postgresql": "18db5afe2c1e80d1ee652b54", "snowflake": "18db5afe2c1e80d1ee652b54", @@ -7178,12 +7178,12 @@ { "DuckDB": "5e46ad7c70253689a5c6bb7f", "None": "5e46ad7c70253689a5c6bb7f", -"bigquery": "5e46ad7c70253689a5c6bb7f", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "5e46ad7c70253689a5c6bb7f", "duckdb": "5e46ad7c70253689a5c6bb7f", "generic": "5e46ad7c70253689a5c6bb7f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5e46ad7c70253689a5c6bb7f", +"motherduck": "5e46ad7c70253689a5c6bb7f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5e46ad7c70253689a5c6bb7f", "postgresql": "5e46ad7c70253689a5c6bb7f", "snowflake": "5e46ad7c70253689a5c6bb7f", @@ -7194,17 +7194,17 @@ "type", { "DuckDB": "3147a9e06daa6de4a4df5abc", -"None": "3147a9e06daa6de4a4df5abc", -"bigquery": "f276e907770196606577c932", -"databricks": "3147a9e06daa6de4a4df5abc", +"None": "da0625706f12b764a6912278", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "da0625706f12b764a6912278", "duckdb": "3147a9e06daa6de4a4df5abc", -"generic": "3147a9e06daa6de4a4df5abc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3147a9e06daa6de4a4df5abc", +"generic": "da0625706f12b764a6912278", +"motherduck": "3147a9e06daa6de4a4df5abc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3147a9e06daa6de4a4df5abc", "postgresql": "3147a9e06daa6de4a4df5abc", "snowflake": "67b5874a2c569226c490211f", -"tsql": "3147a9e06daa6de4a4df5abc" +"tsql": "da0625706f12b764a6912278" } ], [ @@ -7216,8 +7216,8 @@ "databricks": "8789309b90fc585ddbbe90f9", "duckdb": "8789309b90fc585ddbbe90f9", "generic": "8789309b90fc585ddbbe90f9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8789309b90fc585ddbbe90f9", +"motherduck": "8789309b90fc585ddbbe90f9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8789309b90fc585ddbbe90f9", "postgresql": "8789309b90fc585ddbbe90f9", "snowflake": "8789309b90fc585ddbbe90f9", @@ -7233,8 +7233,8 @@ "databricks": "5990107b647ce24001477492", "duckdb": "5990107b647ce24001477492", "generic": "5990107b647ce24001477492", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5990107b647ce24001477492", +"motherduck": "5990107b647ce24001477492", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5990107b647ce24001477492", "postgresql": "5990107b647ce24001477492", "snowflake": "5990107b647ce24001477492", @@ -7250,8 +7250,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -7267,8 +7267,8 @@ "databricks": "f15c56475abfc73488abe63b", "duckdb": "f15c56475abfc73488abe63b", "generic": "f15c56475abfc73488abe63b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f15c56475abfc73488abe63b", +"motherduck": "f15c56475abfc73488abe63b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f15c56475abfc73488abe63b", "postgresql": "f15c56475abfc73488abe63b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -7284,10 +7284,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -7296,15 +7296,15 @@ "type", { "DuckDB": "8317b957016bdf11135b4836", -"None": "0a21e78538ff353d773b4eea", -"bigquery": "0a21e78538ff353d773b4eea", -"databricks": "0a21e78538ff353d773b4eea", +"None": "45310b1154cae517b4caedff", +"bigquery": "45310b1154cae517b4caedff", +"databricks": "45310b1154cae517b4caedff", "duckdb": "8317b957016bdf11135b4836", -"generic": "0a21e78538ff353d773b4eea", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "8317b957016bdf11135b4836", -"postgresql": "8317b957016bdf11135b4836", +"generic": "45310b1154cae517b4caedff", +"motherduck": "8317b957016bdf11135b4836", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "45310b1154cae517b4caedff", +"postgresql": "45310b1154cae517b4caedff", "snowflake": "c2976683dc7003c9f4e8d7d0", "tsql": "45310b1154cae517b4caedff" } @@ -7312,18 +7312,18 @@ [ "type", { -"DuckDB": "e72e438b895cc91eaf4be018", -"None": "a1b6a3abd5c2785b419400de", -"bigquery": "f4105e8e41859869d2746e1d", -"databricks": "a1b6a3abd5c2785b419400de", -"duckdb": "e72e438b895cc91eaf4be018", -"generic": "a1b6a3abd5c2785b419400de", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a1b6a3abd5c2785b419400de", -"postgres": "a1b6a3abd5c2785b419400de", -"postgresql": "a1b6a3abd5c2785b419400de", +"DuckDB": "f77bad2342e566026c6930f9", +"None": "325bc7668cc9012c12196cbf", +"bigquery": "325bc7668cc9012c12196cbf", +"databricks": "325bc7668cc9012c12196cbf", +"duckdb": "f77bad2342e566026c6930f9", +"generic": "325bc7668cc9012c12196cbf", +"motherduck": "f77bad2342e566026c6930f9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "6031cfaf0d57e25f8a111524", +"postgresql": "6031cfaf0d57e25f8a111524", "snowflake": "6031cfaf0d57e25f8a111524", -"tsql": "a1b6a3abd5c2785b419400de" +"tsql": "325bc7668cc9012c12196cbf" } ], [ @@ -7335,8 +7335,8 @@ "databricks": "eea1002d88cc68fad7424aca", "duckdb": "c729c38d2df6efedc6288247", "generic": "eea1002d88cc68fad7424aca", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eea1002d88cc68fad7424aca", +"motherduck": "c729c38d2df6efedc6288247", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eea1002d88cc68fad7424aca", "postgresql": "eea1002d88cc68fad7424aca", "snowflake": "eea1002d88cc68fad7424aca", @@ -7347,17 +7347,17 @@ "type", { "DuckDB": "1ebac487198b6c90b027faab", -"None": "1ebac487198b6c90b027faab", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "1ebac487198b6c90b027faab", +"None": "1eb7fb4e8cf75773c5573562", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1ebac487198b6c90b027faab", -"generic": "1ebac487198b6c90b027faab", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1ebac487198b6c90b027faab", -"postgres": "1ebac487198b6c90b027faab", -"postgresql": "1ebac487198b6c90b027faab", -"snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "c692d974ee3f362ecb38f27c" +"generic": "1eb7fb4e8cf75773c5573562", +"motherduck": "1ebac487198b6c90b027faab", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "1eb7fb4e8cf75773c5573562", +"postgresql": "1eb7fb4e8cf75773c5573562", +"snowflake": "97a20344d811fb77c7690863", +"tsql": "1eb7fb4e8cf75773c5573562" } ], [ @@ -7369,8 +7369,8 @@ "databricks": "1a804f317a3e9a0016925b04", "duckdb": "110327dc45eac0e8d54a96dc", "generic": "110327dc45eac0e8d54a96dc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1a804f317a3e9a0016925b04", +"motherduck": "110327dc45eac0e8d54a96dc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "110327dc45eac0e8d54a96dc", "postgresql": "110327dc45eac0e8d54a96dc", "snowflake": "110327dc45eac0e8d54a96dc", @@ -7386,8 +7386,8 @@ "databricks": "8562dfdaf4f03fa06585b288", "duckdb": "8562dfdaf4f03fa06585b288", "generic": "8562dfdaf4f03fa06585b288", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8562dfdaf4f03fa06585b288", +"motherduck": "8562dfdaf4f03fa06585b288", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8562dfdaf4f03fa06585b288", "postgresql": "8562dfdaf4f03fa06585b288", "snowflake": "8562dfdaf4f03fa06585b288", @@ -7403,8 +7403,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -7416,14 +7416,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -7437,8 +7437,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -7454,8 +7454,8 @@ "databricks": "0111ff4826a58e7709648841", "duckdb": "a0f9ede1ffaaa874ee2066b3", "generic": "a0f9ede1ffaaa874ee2066b3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a0f9ede1ffaaa874ee2066b3", +"motherduck": "a0f9ede1ffaaa874ee2066b3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "be386e1639549e84c97742c5", "postgresql": "be386e1639549e84c97742c5", "snowflake": "8c43a62af28ed0ed164b472b", @@ -7471,8 +7471,8 @@ "databricks": "733b04338829c5844270d25e", "duckdb": "733b04338829c5844270d25e", "generic": "733b04338829c5844270d25e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "733b04338829c5844270d25e", +"motherduck": "733b04338829c5844270d25e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "733b04338829c5844270d25e", "postgresql": "733b04338829c5844270d25e", "snowflake": "733b04338829c5844270d25e", @@ -7483,17 +7483,17 @@ "type", { "DuckDB": "5abf4ec799a2ec32fb10da7e", -"None": "5abf4ec799a2ec32fb10da7e", +"None": "de89632e277dbc8c97115419", "bigquery": "5abf4ec799a2ec32fb10da7e", -"databricks": "5abf4ec799a2ec32fb10da7e", +"databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", -"generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"generic": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", -"tsql": "5abf4ec799a2ec32fb10da7e" +"tsql": "de89632e277dbc8c97115419" } ], [ @@ -7501,14 +7501,14 @@ { "DuckDB": "08736485547ba339930697e9", "None": "08736485547ba339930697e9", -"bigquery": "08736485547ba339930697e9", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "08736485547ba339930697e9", "duckdb": "08736485547ba339930697e9", "generic": "08736485547ba339930697e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "08736485547ba339930697e9", -"postgres": "08736485547ba339930697e9", -"postgresql": "08736485547ba339930697e9", +"motherduck": "08736485547ba339930697e9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eecd452b4f5f3f4133d0bd0a", +"postgresql": "eecd452b4f5f3f4133d0bd0a", "snowflake": "08736485547ba339930697e9", "tsql": "08736485547ba339930697e9" } @@ -7518,14 +7518,14 @@ { "DuckDB": "7cd31289a5b99b5a5e24492d", "None": "7cd31289a5b99b5a5e24492d", -"bigquery": "7cd31289a5b99b5a5e24492d", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "7cd31289a5b99b5a5e24492d", "duckdb": "7cd31289a5b99b5a5e24492d", "generic": "7cd31289a5b99b5a5e24492d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7cd31289a5b99b5a5e24492d", -"postgres": "7cd31289a5b99b5a5e24492d", -"postgresql": "7cd31289a5b99b5a5e24492d", +"motherduck": "7cd31289a5b99b5a5e24492d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c5bdaf88df7ef3d9ae693106", +"postgresql": "c5bdaf88df7ef3d9ae693106", "snowflake": "7cd31289a5b99b5a5e24492d", "tsql": "7cd31289a5b99b5a5e24492d" } @@ -7539,8 +7539,8 @@ "databricks": "a9d9e03f8e981cec9dca7694", "duckdb": "a9d9e03f8e981cec9dca7694", "generic": "a9d9e03f8e981cec9dca7694", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a9d9e03f8e981cec9dca7694", +"motherduck": "a9d9e03f8e981cec9dca7694", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a9d9e03f8e981cec9dca7694", "postgresql": "a9d9e03f8e981cec9dca7694", "snowflake": "a9d9e03f8e981cec9dca7694", @@ -7556,10 +7556,10 @@ "databricks": "8b8d5f75ba6dec68b4cebc6b", "duckdb": "8b8d5f75ba6dec68b4cebc6b", "generic": "8b8d5f75ba6dec68b4cebc6b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8b8d5f75ba6dec68b4cebc6b", -"postgres": "8b8d5f75ba6dec68b4cebc6b", -"postgresql": "8b8d5f75ba6dec68b4cebc6b", +"motherduck": "8b8d5f75ba6dec68b4cebc6b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7914189ff18b0e70ddf7d042", +"postgresql": "7914189ff18b0e70ddf7d042", "snowflake": "8b8d5f75ba6dec68b4cebc6b", "tsql": "8b8d5f75ba6dec68b4cebc6b" } @@ -7573,8 +7573,8 @@ "databricks": "3959952046272deb40567543", "duckdb": "3959952046272deb40567543", "generic": "3959952046272deb40567543", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3959952046272deb40567543", +"motherduck": "3959952046272deb40567543", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3959952046272deb40567543", "postgresql": "3959952046272deb40567543", "snowflake": "3959952046272deb40567543", @@ -7590,8 +7590,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "c5d56d00f80f43bfec07a7c2", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "c5d56d00f80f43bfec07a7c2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -7607,8 +7607,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "251aba3fef503ce64556c701", "postgresql": "251aba3fef503ce64556c701", "snowflake": "c50a316d091c821866e4bfba", @@ -7624,8 +7624,8 @@ "databricks": "5855bf076c2cc936300b1e0a", "duckdb": "5855bf076c2cc936300b1e0a", "generic": "5855bf076c2cc936300b1e0a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5855bf076c2cc936300b1e0a", +"motherduck": "5855bf076c2cc936300b1e0a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5855bf076c2cc936300b1e0a", "postgresql": "5855bf076c2cc936300b1e0a", "snowflake": "5855bf076c2cc936300b1e0a", @@ -7636,17 +7636,17 @@ "type", { "DuckDB": "f2ed52f6bf46b4db9343551c", -"None": "f2ed52f6bf46b4db9343551c", -"bigquery": "f10bdd4d40eb495cbcecd31d", -"databricks": "f2ed52f6bf46b4db9343551c", +"None": "3f998a9ab68d8b2514840489", +"bigquery": "cc809c2a2f4e06379d6e4fe5", +"databricks": "3f998a9ab68d8b2514840489", "duckdb": "f2ed52f6bf46b4db9343551c", -"generic": "f2ed52f6bf46b4db9343551c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f2ed52f6bf46b4db9343551c", -"postgres": "f2ed52f6bf46b4db9343551c", -"postgresql": "f2ed52f6bf46b4db9343551c", +"generic": "3f998a9ab68d8b2514840489", +"motherduck": "f2ed52f6bf46b4db9343551c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7ce41cc5375b618de22b6a4e", +"postgresql": "7ce41cc5375b618de22b6a4e", "snowflake": "f2ed52f6bf46b4db9343551c", -"tsql": "f2ed52f6bf46b4db9343551c" +"tsql": "3f998a9ab68d8b2514840489" } ], [ @@ -7658,10 +7658,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -7675,8 +7675,8 @@ "databricks": "42f8fe02ce664d09ff39167c", "duckdb": "42f8fe02ce664d09ff39167c", "generic": "42f8fe02ce664d09ff39167c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "42f8fe02ce664d09ff39167c", +"motherduck": "42f8fe02ce664d09ff39167c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "42f8fe02ce664d09ff39167c", "postgresql": "42f8fe02ce664d09ff39167c", "snowflake": "42f8fe02ce664d09ff39167c", @@ -7687,17 +7687,17 @@ "type", { "DuckDB": "4ebb6333caac069be16c795d", -"None": "4ebb6333caac069be16c795d", -"bigquery": "4ebb6333caac069be16c795d", -"databricks": "4ebb6333caac069be16c795d", +"None": "d5ce089ec505733f59838ffd", +"bigquery": "d5ce089ec505733f59838ffd", +"databricks": "d5ce089ec505733f59838ffd", "duckdb": "4ebb6333caac069be16c795d", -"generic": "4ebb6333caac069be16c795d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4ebb6333caac069be16c795d", +"generic": "d5ce089ec505733f59838ffd", +"motherduck": "4ebb6333caac069be16c795d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4ebb6333caac069be16c795d", "postgresql": "4ebb6333caac069be16c795d", "snowflake": "4ebb6333caac069be16c795d", -"tsql": "4ebb6333caac069be16c795d" +"tsql": "d5ce089ec505733f59838ffd" } ], [ @@ -7709,8 +7709,8 @@ "databricks": "c844c96e1e3ffc72d5f90df9", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "c844c96e1e3ffc72d5f90df9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28eb0d6de9724959ce5ba43e", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c844c96e1e3ffc72d5f90df9", "postgresql": "c844c96e1e3ffc72d5f90df9", "snowflake": "c844c96e1e3ffc72d5f90df9", @@ -7726,8 +7726,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -7743,8 +7743,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -7760,8 +7760,8 @@ "databricks": "3915f3f73328f0838497b2d5", "duckdb": "3915f3f73328f0838497b2d5", "generic": "3915f3f73328f0838497b2d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3915f3f73328f0838497b2d5", +"motherduck": "3915f3f73328f0838497b2d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3915f3f73328f0838497b2d5", "postgresql": "3915f3f73328f0838497b2d5", "snowflake": "f43dac38f34a1c0618a3f812", @@ -7777,8 +7777,8 @@ "databricks": "52e79b501b430a4c2b097344", "duckdb": "52e79b501b430a4c2b097344", "generic": "52e79b501b430a4c2b097344", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "52e79b501b430a4c2b097344", +"motherduck": "52e79b501b430a4c2b097344", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "52e79b501b430a4c2b097344", "postgresql": "52e79b501b430a4c2b097344", "snowflake": "52e79b501b430a4c2b097344", @@ -7789,17 +7789,17 @@ "type", { "DuckDB": "13f1401e7cd410600a4c9485", -"None": "13f1401e7cd410600a4c9485", -"bigquery": "74a30f6aa9def0b58afc616c", -"databricks": "13f1401e7cd410600a4c9485", +"None": "75c45736873ac5c99a62bc20", +"bigquery": "a686bb6db17ab079cfc79224", +"databricks": "75c45736873ac5c99a62bc20", "duckdb": "13f1401e7cd410600a4c9485", -"generic": "13f1401e7cd410600a4c9485", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "13f1401e7cd410600a4c9485", +"generic": "75c45736873ac5c99a62bc20", +"motherduck": "13f1401e7cd410600a4c9485", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "13f1401e7cd410600a4c9485", "postgresql": "13f1401e7cd410600a4c9485", "snowflake": "9b5f44707b5f4764b82741be", -"tsql": "13f1401e7cd410600a4c9485" +"tsql": "75c45736873ac5c99a62bc20" } ], [ @@ -7807,14 +7807,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -7828,8 +7828,8 @@ "databricks": "802f1c1fcb97ddd5679172d2", "duckdb": "802f1c1fcb97ddd5679172d2", "generic": "802f1c1fcb97ddd5679172d2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "802f1c1fcb97ddd5679172d2", +"motherduck": "802f1c1fcb97ddd5679172d2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "802f1c1fcb97ddd5679172d2", "postgresql": "802f1c1fcb97ddd5679172d2", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -7845,8 +7845,8 @@ "databricks": "60a23b7dcae6c79f4de83068", "duckdb": "60a23b7dcae6c79f4de83068", "generic": "60a23b7dcae6c79f4de83068", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60a23b7dcae6c79f4de83068", +"motherduck": "60a23b7dcae6c79f4de83068", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "60a23b7dcae6c79f4de83068", "postgresql": "60a23b7dcae6c79f4de83068", "snowflake": "60a23b7dcae6c79f4de83068", @@ -7858,14 +7858,14 @@ { "DuckDB": "4b356542044f1267fcfc487a", "None": "4b356542044f1267fcfc487a", -"bigquery": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", "databricks": "4b356542044f1267fcfc487a", "duckdb": "4b356542044f1267fcfc487a", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "4b356542044f1267fcfc487a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -7879,8 +7879,8 @@ "databricks": "3315bb12afea7b182498ce28", "duckdb": "3315bb12afea7b182498ce28", "generic": "3315bb12afea7b182498ce28", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3315bb12afea7b182498ce28", +"motherduck": "3315bb12afea7b182498ce28", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3315bb12afea7b182498ce28", "postgresql": "3315bb12afea7b182498ce28", "snowflake": "3315bb12afea7b182498ce28", @@ -7896,8 +7896,8 @@ "databricks": "916f1a306af797bd328aec64", "duckdb": "2844656bba2e199281ab8d9b", "generic": "2844656bba2e199281ab8d9b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "916f1a306af797bd328aec64", +"motherduck": "2844656bba2e199281ab8d9b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2844656bba2e199281ab8d9b", "postgresql": "2844656bba2e199281ab8d9b", "snowflake": "2844656bba2e199281ab8d9b", @@ -7913,8 +7913,8 @@ "databricks": "e62af34c2d31bc9ab880629e", "duckdb": "8a7aa7fa75696327320e0add", "generic": "8a7aa7fa75696327320e0add", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e62af34c2d31bc9ab880629e", +"motherduck": "8a7aa7fa75696327320e0add", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a7aa7fa75696327320e0add", "postgresql": "8a7aa7fa75696327320e0add", "snowflake": "8a7aa7fa75696327320e0add", @@ -7930,11 +7930,11 @@ "databricks": "cbea30a905a72d3b17dc7211", "duckdb": "cbea30a905a72d3b17dc7211", "generic": "cbea30a905a72d3b17dc7211", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cbea30a905a72d3b17dc7211", +"motherduck": "cbea30a905a72d3b17dc7211", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cbea30a905a72d3b17dc7211", "postgresql": "cbea30a905a72d3b17dc7211", -"snowflake": "cbea30a905a72d3b17dc7211", +"snowflake": "7d597ede6f7cf27affdde1b0", "tsql": "cbea30a905a72d3b17dc7211" } ], @@ -7947,8 +7947,8 @@ "databricks": "f15c56475abfc73488abe63b", "duckdb": "f15c56475abfc73488abe63b", "generic": "f15c56475abfc73488abe63b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f15c56475abfc73488abe63b", +"motherduck": "f15c56475abfc73488abe63b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f15c56475abfc73488abe63b", "postgresql": "f15c56475abfc73488abe63b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -7964,8 +7964,8 @@ "databricks": "e8b6e0bd2ded7b9eec6209e4", "duckdb": "e8b6e0bd2ded7b9eec6209e4", "generic": "e8b6e0bd2ded7b9eec6209e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8b6e0bd2ded7b9eec6209e4", +"motherduck": "e8b6e0bd2ded7b9eec6209e4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8b6e0bd2ded7b9eec6209e4", "postgresql": "e8b6e0bd2ded7b9eec6209e4", "snowflake": "e8b6e0bd2ded7b9eec6209e4", @@ -7981,8 +7981,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "1d8b945fd793ecdcacb90c21", "generic": "1d8b945fd793ecdcacb90c21", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1d8b945fd793ecdcacb90c21", +"motherduck": "1d8b945fd793ecdcacb90c21", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3efe8bb57c98fcbe74096b3c", "postgresql": "3efe8bb57c98fcbe74096b3c", "snowflake": "1d8b945fd793ecdcacb90c21", @@ -7998,10 +7998,10 @@ "databricks": "4e10d2f9eb8994ead08d1761", "duckdb": "4e10d2f9eb8994ead08d1761", "generic": "4e10d2f9eb8994ead08d1761", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4e10d2f9eb8994ead08d1761", -"postgres": "4e10d2f9eb8994ead08d1761", -"postgresql": "4e10d2f9eb8994ead08d1761", +"motherduck": "4e10d2f9eb8994ead08d1761", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f7f38c204fc2a5b7e7986a31", +"postgresql": "f7f38c204fc2a5b7e7986a31", "snowflake": "4e10d2f9eb8994ead08d1761", "tsql": "4e10d2f9eb8994ead08d1761" } @@ -8015,8 +8015,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -8032,8 +8032,8 @@ "databricks": "46ec3e19e05a6cb29854ccac", "duckdb": "35e216b6921bf843994f2eea", "generic": "46ec3e19e05a6cb29854ccac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "46ec3e19e05a6cb29854ccac", +"motherduck": "35e216b6921bf843994f2eea", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "46ec3e19e05a6cb29854ccac", "postgresql": "46ec3e19e05a6cb29854ccac", "snowflake": "46ec3e19e05a6cb29854ccac", @@ -8044,17 +8044,17 @@ "type", { "DuckDB": "7515c0db413239044adef879", -"None": "7515c0db413239044adef879", -"bigquery": "f276e907770196606577c932", -"databricks": "7515c0db413239044adef879", +"None": "d4b89e61a66aeb076db92954", +"bigquery": "bb92c01c64d1a2d32abb67b5", +"databricks": "d4b89e61a66aeb076db92954", "duckdb": "7515c0db413239044adef879", -"generic": "7515c0db413239044adef879", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7515c0db413239044adef879", +"generic": "d4b89e61a66aeb076db92954", +"motherduck": "7515c0db413239044adef879", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7515c0db413239044adef879", "postgresql": "7515c0db413239044adef879", "snowflake": "67b5874a2c569226c490211f", -"tsql": "7515c0db413239044adef879" +"tsql": "d4b89e61a66aeb076db92954" } ], [ @@ -8066,8 +8066,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -8083,8 +8083,8 @@ "databricks": "4662a1758442ce44b830269f", "duckdb": "4662a1758442ce44b830269f", "generic": "4662a1758442ce44b830269f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4662a1758442ce44b830269f", +"motherduck": "4662a1758442ce44b830269f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4662a1758442ce44b830269f", "postgresql": "4662a1758442ce44b830269f", "snowflake": "4662a1758442ce44b830269f", @@ -8095,15 +8095,15 @@ "type", { "DuckDB": "a55ce7977ac874a3b204217e", -"None": "86b982a98982cd844f498c36", -"bigquery": "86b982a98982cd844f498c36", -"databricks": "86b982a98982cd844f498c36", +"None": "a13763a505b15f311cd22d08", +"bigquery": "a13763a505b15f311cd22d08", +"databricks": "a13763a505b15f311cd22d08", "duckdb": "a55ce7977ac874a3b204217e", -"generic": "86b982a98982cd844f498c36", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "a55ce7977ac874a3b204217e", -"postgresql": "a55ce7977ac874a3b204217e", +"generic": "a13763a505b15f311cd22d08", +"motherduck": "a55ce7977ac874a3b204217e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a13763a505b15f311cd22d08", +"postgresql": "a13763a505b15f311cd22d08", "snowflake": "5a4429eb67014c1c8f9e10d4", "tsql": "a13763a505b15f311cd22d08" } @@ -8113,12 +8113,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -8134,10 +8134,10 @@ "databricks": "bbb97828cd28c64282709796", "duckdb": "bbb97828cd28c64282709796", "generic": "bbb97828cd28c64282709796", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bbb97828cd28c64282709796", -"postgres": "bbb97828cd28c64282709796", -"postgresql": "bbb97828cd28c64282709796", +"motherduck": "bbb97828cd28c64282709796", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b210f8eb26c37b422f786c6b", +"postgresql": "b210f8eb26c37b422f786c6b", "snowflake": "bbb97828cd28c64282709796", "tsql": "bbb97828cd28c64282709796" } @@ -8151,8 +8151,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -8164,14 +8164,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -8180,15 +8180,15 @@ "type", { "DuckDB": "02861a11fe2b8f1a7351cb12", -"None": "3abc5e3c14192017bfd1ead5", +"None": "f2c7751cca55dc87c36b6fc2", "bigquery": "f2c7751cca55dc87c36b6fc2", "databricks": "f2c7751cca55dc87c36b6fc2", "duckdb": "02861a11fe2b8f1a7351cb12", -"generic": "3abc5e3c14192017bfd1ead5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f2c7751cca55dc87c36b6fc2", -"postgres": "35efca2fc5df9bbd4013257a", -"postgresql": "35efca2fc5df9bbd4013257a", +"generic": "f2c7751cca55dc87c36b6fc2", +"motherduck": "02861a11fe2b8f1a7351cb12", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f2c7751cca55dc87c36b6fc2", +"postgresql": "f2c7751cca55dc87c36b6fc2", "snowflake": "5b65e3054ea3260a0a6dd837", "tsql": "f2c7751cca55dc87c36b6fc2" } @@ -8202,8 +8202,8 @@ "databricks": "efe77000c8cb77e8bcc49328", "duckdb": "d1a9a884331f069d89b0935f", "generic": "d1a9a884331f069d89b0935f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "efe77000c8cb77e8bcc49328", +"motherduck": "d1a9a884331f069d89b0935f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d1a9a884331f069d89b0935f", "postgresql": "d1a9a884331f069d89b0935f", "snowflake": "d1a9a884331f069d89b0935f", @@ -8219,8 +8219,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -8236,10 +8236,10 @@ "databricks": "bce60580ef3a1dc4ce569fff", "duckdb": "bce60580ef3a1dc4ce569fff", "generic": "bce60580ef3a1dc4ce569fff", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bce60580ef3a1dc4ce569fff", -"postgres": "bce60580ef3a1dc4ce569fff", -"postgresql": "bce60580ef3a1dc4ce569fff", +"motherduck": "bce60580ef3a1dc4ce569fff", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eee236ea1427bdddbaf088ac", +"postgresql": "eee236ea1427bdddbaf088ac", "snowflake": "bce60580ef3a1dc4ce569fff", "tsql": "bce60580ef3a1dc4ce569fff" } @@ -8249,14 +8249,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -8270,8 +8270,8 @@ "databricks": "cc33965a669608ec39e8c246", "duckdb": "cc33965a669608ec39e8c246", "generic": "cc33965a669608ec39e8c246", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cc33965a669608ec39e8c246", +"motherduck": "cc33965a669608ec39e8c246", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cc33965a669608ec39e8c246", "postgresql": "cc33965a669608ec39e8c246", "snowflake": "cc33965a669608ec39e8c246", @@ -8287,10 +8287,10 @@ "databricks": "4381456d770bb42050076d00", "duckdb": "4381456d770bb42050076d00", "generic": "4381456d770bb42050076d00", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4381456d770bb42050076d00", -"postgres": "4381456d770bb42050076d00", -"postgresql": "4381456d770bb42050076d00", +"motherduck": "4381456d770bb42050076d00", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "1f89b04f39a23f492cb6e5eb", +"postgresql": "1f89b04f39a23f492cb6e5eb", "snowflake": "4381456d770bb42050076d00", "tsql": "4381456d770bb42050076d00" } @@ -8304,8 +8304,8 @@ "databricks": "a34c63be87b77d81239d0b4b", "duckdb": "a34c63be87b77d81239d0b4b", "generic": "a34c63be87b77d81239d0b4b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a34c63be87b77d81239d0b4b", +"motherduck": "a34c63be87b77d81239d0b4b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a34c63be87b77d81239d0b4b", "postgresql": "a34c63be87b77d81239d0b4b", "snowflake": "a34c63be87b77d81239d0b4b", @@ -8321,8 +8321,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -8338,8 +8338,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -8352,11 +8352,11 @@ "DuckDB": "0d58e20cd83f047b3b85cd1c", "None": "0d58e20cd83f047b3b85cd1c", "bigquery": "0d58e20cd83f047b3b85cd1c", -"databricks": "9f2957c784cb1f6b640c10f7", +"databricks": "0d58e20cd83f047b3b85cd1c", "duckdb": "0d58e20cd83f047b3b85cd1c", "generic": "0d58e20cd83f047b3b85cd1c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9f2957c784cb1f6b640c10f7", +"motherduck": "0d58e20cd83f047b3b85cd1c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0d58e20cd83f047b3b85cd1c", "postgresql": "0d58e20cd83f047b3b85cd1c", "snowflake": "0d58e20cd83f047b3b85cd1c", @@ -8368,14 +8368,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -8389,8 +8389,8 @@ "databricks": "dba9493579be12efd69318ac", "duckdb": "63e20906fe3504356e04d05e", "generic": "dba9493579be12efd69318ac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "63e20906fe3504356e04d05e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "63e20906fe3504356e04d05e", "postgresql": "63e20906fe3504356e04d05e", "snowflake": "30696690153e91e7ae197982", @@ -8406,8 +8406,8 @@ "databricks": "7914189ff18b0e70ddf7d042", "duckdb": "7914189ff18b0e70ddf7d042", "generic": "7914189ff18b0e70ddf7d042", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7914189ff18b0e70ddf7d042", +"motherduck": "7914189ff18b0e70ddf7d042", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7914189ff18b0e70ddf7d042", "postgresql": "7914189ff18b0e70ddf7d042", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -8418,17 +8418,17 @@ "type", { "DuckDB": "919b7ec6a7b49d8527b784cd", -"None": "919b7ec6a7b49d8527b784cd", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "e3bc1b8bad22d8104417b00f", +"None": "558da38ec4d9d5d6c72f60cf", +"bigquery": "c50a316d091c821866e4bfba", +"databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "919b7ec6a7b49d8527b784cd", -"generic": "919b7ec6a7b49d8527b784cd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "919b7ec6a7b49d8527b784cd", -"postgres": "a3b6b25265be330ec52a3322", -"postgresql": "a3b6b25265be330ec52a3322", +"generic": "558da38ec4d9d5d6c72f60cf", +"motherduck": "919b7ec6a7b49d8527b784cd", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "558da38ec4d9d5d6c72f60cf", +"postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "919b7ec6a7b49d8527b784cd", -"tsql": "919b7ec6a7b49d8527b784cd" +"tsql": "558da38ec4d9d5d6c72f60cf" } ], [ @@ -8440,8 +8440,8 @@ "databricks": "42cd2b5892468e185781348b", "duckdb": "42cd2b5892468e185781348b", "generic": "42cd2b5892468e185781348b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "42cd2b5892468e185781348b", +"motherduck": "42cd2b5892468e185781348b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "42cd2b5892468e185781348b", "postgresql": "42cd2b5892468e185781348b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -8457,8 +8457,8 @@ "databricks": "46ec3e19e05a6cb29854ccac", "duckdb": "35e216b6921bf843994f2eea", "generic": "46ec3e19e05a6cb29854ccac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "46ec3e19e05a6cb29854ccac", +"motherduck": "35e216b6921bf843994f2eea", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "46ec3e19e05a6cb29854ccac", "postgresql": "46ec3e19e05a6cb29854ccac", "snowflake": "46ec3e19e05a6cb29854ccac", @@ -8474,8 +8474,8 @@ "databricks": "eed0645c1e86fa1a6e35ccaa", "duckdb": "eed0645c1e86fa1a6e35ccaa", "generic": "eed0645c1e86fa1a6e35ccaa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eed0645c1e86fa1a6e35ccaa", +"motherduck": "eed0645c1e86fa1a6e35ccaa", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eed0645c1e86fa1a6e35ccaa", "postgresql": "eed0645c1e86fa1a6e35ccaa", "snowflake": "97a20344d811fb77c7690863", @@ -8491,8 +8491,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -8508,8 +8508,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -8522,11 +8522,11 @@ "DuckDB": "0f3d33062aebbc5edec7b64f", "None": "0f3d33062aebbc5edec7b64f", "bigquery": "0f3d33062aebbc5edec7b64f", -"databricks": "544d1979b70121c2f5770b6a", +"databricks": "0f3d33062aebbc5edec7b64f", "duckdb": "0f3d33062aebbc5edec7b64f", "generic": "0f3d33062aebbc5edec7b64f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "544d1979b70121c2f5770b6a", +"motherduck": "0f3d33062aebbc5edec7b64f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0f3d33062aebbc5edec7b64f", "postgresql": "0f3d33062aebbc5edec7b64f", "snowflake": "0f3d33062aebbc5edec7b64f", @@ -8542,8 +8542,8 @@ "databricks": "b2c9b6ebca6c097f3caa0aa8", "duckdb": "b2c9b6ebca6c097f3caa0aa8", "generic": "b2c9b6ebca6c097f3caa0aa8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b2c9b6ebca6c097f3caa0aa8", +"motherduck": "b2c9b6ebca6c097f3caa0aa8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b2c9b6ebca6c097f3caa0aa8", "postgresql": "b2c9b6ebca6c097f3caa0aa8", "snowflake": "b2c9b6ebca6c097f3caa0aa8", @@ -8559,8 +8559,8 @@ "databricks": "c21a08c5ec73e14b4d409300", "duckdb": "c21a08c5ec73e14b4d409300", "generic": "c21a08c5ec73e14b4d409300", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c21a08c5ec73e14b4d409300", +"motherduck": "c21a08c5ec73e14b4d409300", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c21a08c5ec73e14b4d409300", "postgresql": "c21a08c5ec73e14b4d409300", "snowflake": "c21a08c5ec73e14b4d409300", @@ -8572,14 +8572,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -8593,8 +8593,8 @@ "databricks": "88582da8542d7d1abf567520", "duckdb": "88582da8542d7d1abf567520", "generic": "88582da8542d7d1abf567520", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "88582da8542d7d1abf567520", +"motherduck": "88582da8542d7d1abf567520", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "88582da8542d7d1abf567520", "postgresql": "88582da8542d7d1abf567520", "snowflake": "88582da8542d7d1abf567520", @@ -8610,8 +8610,8 @@ "databricks": "8a10388471810a2bd58f7686", "duckdb": "8a10388471810a2bd58f7686", "generic": "8a10388471810a2bd58f7686", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8a10388471810a2bd58f7686", +"motherduck": "8a10388471810a2bd58f7686", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a10388471810a2bd58f7686", "postgresql": "8a10388471810a2bd58f7686", "snowflake": "8a10388471810a2bd58f7686", @@ -8627,8 +8627,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -8644,10 +8644,10 @@ "databricks": "64fc778308b798a730b1d5c9", "duckdb": "64fc778308b798a730b1d5c9", "generic": "64fc778308b798a730b1d5c9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "64fc778308b798a730b1d5c9", -"postgres": "64fc778308b798a730b1d5c9", -"postgresql": "64fc778308b798a730b1d5c9", +"motherduck": "64fc778308b798a730b1d5c9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c8ca7a940ad228dd836cad6f", +"postgresql": "c8ca7a940ad228dd836cad6f", "snowflake": "64fc778308b798a730b1d5c9", "tsql": "64fc778308b798a730b1d5c9" } @@ -8661,8 +8661,8 @@ "databricks": "679a90349a55ba7765c07664", "duckdb": "679a90349a55ba7765c07664", "generic": "679a90349a55ba7765c07664", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "679a90349a55ba7765c07664", +"motherduck": "679a90349a55ba7765c07664", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "679a90349a55ba7765c07664", "postgresql": "679a90349a55ba7765c07664", "snowflake": "679a90349a55ba7765c07664", @@ -8678,8 +8678,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -8690,30 +8690,30 @@ "type", { "DuckDB": "3147a9e06daa6de4a4df5abc", -"None": "3147a9e06daa6de4a4df5abc", -"bigquery": "f276e907770196606577c932", -"databricks": "3147a9e06daa6de4a4df5abc", +"None": "da0625706f12b764a6912278", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "da0625706f12b764a6912278", "duckdb": "3147a9e06daa6de4a4df5abc", -"generic": "3147a9e06daa6de4a4df5abc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3147a9e06daa6de4a4df5abc", +"generic": "da0625706f12b764a6912278", +"motherduck": "3147a9e06daa6de4a4df5abc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3147a9e06daa6de4a4df5abc", "postgresql": "3147a9e06daa6de4a4df5abc", "snowflake": "67b5874a2c569226c490211f", -"tsql": "3147a9e06daa6de4a4df5abc" +"tsql": "da0625706f12b764a6912278" } ], [ "type", { "DuckDB": "ff99ba717583eafc6fdd9442", -"None": "a915140f475538632ca59017", -"bigquery": "a915140f475538632ca59017", -"databricks": "a915140f475538632ca59017", +"None": "3b16d8eb223ae1333ecbc2bf", +"bigquery": "3b16d8eb223ae1333ecbc2bf", +"databricks": "3b16d8eb223ae1333ecbc2bf", "duckdb": "ff99ba717583eafc6fdd9442", -"generic": "a915140f475538632ca59017", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"generic": "3b16d8eb223ae1333ecbc2bf", +"motherduck": "ff99ba717583eafc6fdd9442", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "39ac50273811909ae35979b7", "postgresql": "39ac50273811909ae35979b7", "snowflake": "ca443b97e784bced05b62f0b", @@ -8729,8 +8729,8 @@ "databricks": "e7683084f9cefaf3748cf526", "duckdb": "e7683084f9cefaf3748cf526", "generic": "e7683084f9cefaf3748cf526", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7683084f9cefaf3748cf526", +"motherduck": "e7683084f9cefaf3748cf526", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7683084f9cefaf3748cf526", "postgresql": "e7683084f9cefaf3748cf526", "snowflake": "e7683084f9cefaf3748cf526", @@ -8746,8 +8746,8 @@ "databricks": "9626d393c137a37008bb4b89", "duckdb": "9626d393c137a37008bb4b89", "generic": "9626d393c137a37008bb4b89", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9626d393c137a37008bb4b89", +"motherduck": "9626d393c137a37008bb4b89", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9626d393c137a37008bb4b89", "postgresql": "9626d393c137a37008bb4b89", "snowflake": "9626d393c137a37008bb4b89", @@ -8763,10 +8763,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -8780,8 +8780,8 @@ "databricks": "4e47cae1f2f84f5e523dc43f", "duckdb": "4e47cae1f2f84f5e523dc43f", "generic": "4e47cae1f2f84f5e523dc43f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4e47cae1f2f84f5e523dc43f", +"motherduck": "4e47cae1f2f84f5e523dc43f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4e47cae1f2f84f5e523dc43f", "postgresql": "4e47cae1f2f84f5e523dc43f", "snowflake": "4e47cae1f2f84f5e523dc43f", @@ -8797,8 +8797,8 @@ "databricks": "8fe65454f387031aa6231faf", "duckdb": "8fe65454f387031aa6231faf", "generic": "8fe65454f387031aa6231faf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8fe65454f387031aa6231faf", +"motherduck": "8fe65454f387031aa6231faf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8fe65454f387031aa6231faf", "postgresql": "8fe65454f387031aa6231faf", "snowflake": "8fe65454f387031aa6231faf", @@ -8809,17 +8809,17 @@ "type", { "DuckDB": "a428302e4848ffe877284c84", -"None": "a428302e4848ffe877284c84", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "a428302e4848ffe877284c84", +"None": "c9b810639795a7bfab91b98d", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "c9b810639795a7bfab91b98d", "duckdb": "a428302e4848ffe877284c84", -"generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"generic": "c9b810639795a7bfab91b98d", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "a428302e4848ffe877284c84" +"tsql": "c9b810639795a7bfab91b98d" } ], [ @@ -8831,8 +8831,8 @@ "databricks": "c844c96e1e3ffc72d5f90df9", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "c844c96e1e3ffc72d5f90df9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28eb0d6de9724959ce5ba43e", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c844c96e1e3ffc72d5f90df9", "postgresql": "c844c96e1e3ffc72d5f90df9", "snowflake": "c844c96e1e3ffc72d5f90df9", @@ -8848,8 +8848,8 @@ "databricks": "e8b6e0bd2ded7b9eec6209e4", "duckdb": "e8b6e0bd2ded7b9eec6209e4", "generic": "e8b6e0bd2ded7b9eec6209e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8b6e0bd2ded7b9eec6209e4", +"motherduck": "e8b6e0bd2ded7b9eec6209e4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8b6e0bd2ded7b9eec6209e4", "postgresql": "e8b6e0bd2ded7b9eec6209e4", "snowflake": "e8b6e0bd2ded7b9eec6209e4", @@ -8861,12 +8861,12 @@ { "DuckDB": "d41d40c68507f4d02e348833", "None": "d41d40c68507f4d02e348833", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "d41d40c68507f4d02e348833", "duckdb": "d41d40c68507f4d02e348833", "generic": "d41d40c68507f4d02e348833", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d41d40c68507f4d02e348833", +"motherduck": "d41d40c68507f4d02e348833", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d41d40c68507f4d02e348833", "postgresql": "d41d40c68507f4d02e348833", "snowflake": "d41d40c68507f4d02e348833", @@ -8878,14 +8878,14 @@ { "DuckDB": "18db5afe2c1e80d1ee652b54", "None": "18db5afe2c1e80d1ee652b54", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", -"postgres": "18db5afe2c1e80d1ee652b54", -"postgresql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "47d64a5994d42a2b577166ea", +"postgresql": "47d64a5994d42a2b577166ea", "snowflake": "18db5afe2c1e80d1ee652b54", "tsql": "18db5afe2c1e80d1ee652b54" } @@ -8899,8 +8899,8 @@ "databricks": "29685ad61061d232d5b44f98", "duckdb": "fbb8dada51c532716b2cbf9f", "generic": "29685ad61061d232d5b44f98", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "fbb8dada51c532716b2cbf9f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fbb8dada51c532716b2cbf9f", "postgresql": "fbb8dada51c532716b2cbf9f", "snowflake": "3d54d99fa15cb697878997d5", @@ -8916,8 +8916,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -8933,8 +8933,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -8945,17 +8945,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -8967,10 +8967,10 @@ "databricks": "64fc778308b798a730b1d5c9", "duckdb": "64fc778308b798a730b1d5c9", "generic": "64fc778308b798a730b1d5c9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "64fc778308b798a730b1d5c9", -"postgres": "64fc778308b798a730b1d5c9", -"postgresql": "64fc778308b798a730b1d5c9", +"motherduck": "64fc778308b798a730b1d5c9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c8ca7a940ad228dd836cad6f", +"postgresql": "c8ca7a940ad228dd836cad6f", "snowflake": "64fc778308b798a730b1d5c9", "tsql": "64fc778308b798a730b1d5c9" } @@ -8984,8 +8984,8 @@ "databricks": "fd3b9424e28fed0134486bc7", "duckdb": "ee8c9d9a91ebec8ca7defc90", "generic": "fd3b9424e28fed0134486bc7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "ee8c9d9a91ebec8ca7defc90", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ee8c9d9a91ebec8ca7defc90", "postgresql": "ee8c9d9a91ebec8ca7defc90", "snowflake": "1d67c9120962e3304f0e1bcc", @@ -9001,8 +9001,8 @@ "databricks": "3a4d8bca5124769db23a43df", "duckdb": "01e3dfc024c37a8d62c7f810", "generic": "3a4d8bca5124769db23a43df", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "01e3dfc024c37a8d62c7f810", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "01e3dfc024c37a8d62c7f810", "postgresql": "01e3dfc024c37a8d62c7f810", "snowflake": "db03b5ccf153c75e43846a6a", @@ -9018,8 +9018,8 @@ "databricks": "791a58207da7e61895ce33f6", "duckdb": "791a58207da7e61895ce33f6", "generic": "791a58207da7e61895ce33f6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "791a58207da7e61895ce33f6", +"motherduck": "791a58207da7e61895ce33f6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "791a58207da7e61895ce33f6", "postgresql": "791a58207da7e61895ce33f6", "snowflake": "791a58207da7e61895ce33f6", @@ -9031,12 +9031,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -9048,14 +9048,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -9064,17 +9064,17 @@ "type", { "DuckDB": "f7fca17b74cc50ae3de8c826", -"None": "f7fca17b74cc50ae3de8c826", -"bigquery": "f7fca17b74cc50ae3de8c826", -"databricks": "f7fca17b74cc50ae3de8c826", +"None": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", +"databricks": "d2c97d221599d038d9875873", "duckdb": "f7fca17b74cc50ae3de8c826", -"generic": "f7fca17b74cc50ae3de8c826", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f7fca17b74cc50ae3de8c826", -"postgres": "f7fca17b74cc50ae3de8c826", -"postgresql": "f7fca17b74cc50ae3de8c826", +"generic": "d2c97d221599d038d9875873", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "55155766823b6a3a5e882fc2", -"tsql": "f7fca17b74cc50ae3de8c826" +"tsql": "d2c97d221599d038d9875873" } ], [ @@ -9086,8 +9086,8 @@ "databricks": "ef34121d80f6c399a1a456b1", "duckdb": "ef34121d80f6c399a1a456b1", "generic": "ef34121d80f6c399a1a456b1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ef34121d80f6c399a1a456b1", +"motherduck": "ef34121d80f6c399a1a456b1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ef34121d80f6c399a1a456b1", "postgresql": "ef34121d80f6c399a1a456b1", "snowflake": "ef34121d80f6c399a1a456b1", @@ -9103,8 +9103,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -9116,14 +9116,14 @@ { "DuckDB": "b4284f955ab1914d3f556d82", "None": "b4284f955ab1914d3f556d82", -"bigquery": "b4284f955ab1914d3f556d82", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "b4284f955ab1914d3f556d82", "duckdb": "b4284f955ab1914d3f556d82", "generic": "b4284f955ab1914d3f556d82", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b4284f955ab1914d3f556d82", -"postgres": "b4284f955ab1914d3f556d82", -"postgresql": "b4284f955ab1914d3f556d82", +"motherduck": "b4284f955ab1914d3f556d82", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "063c794e14d2f13bb89a7a4d", +"postgresql": "063c794e14d2f13bb89a7a4d", "snowflake": "b4284f955ab1914d3f556d82", "tsql": "b4284f955ab1914d3f556d82" } @@ -9133,14 +9133,14 @@ { "DuckDB": "c84c0b1d4d7caa6744130456", "None": "c84c0b1d4d7caa6744130456", -"bigquery": "c84c0b1d4d7caa6744130456", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "c84c0b1d4d7caa6744130456", "duckdb": "c84c0b1d4d7caa6744130456", "generic": "c84c0b1d4d7caa6744130456", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c84c0b1d4d7caa6744130456", -"postgres": "c84c0b1d4d7caa6744130456", -"postgresql": "c84c0b1d4d7caa6744130456", +"motherduck": "c84c0b1d4d7caa6744130456", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "725c73e74f311417bb4f95e8", +"postgresql": "725c73e74f311417bb4f95e8", "snowflake": "c84c0b1d4d7caa6744130456", "tsql": "c84c0b1d4d7caa6744130456" } @@ -9150,12 +9150,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -9171,8 +9171,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -9183,17 +9183,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -9205,8 +9205,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -9218,14 +9218,14 @@ { "DuckDB": "73151c1398b30e5c5d18f4ce", "None": "73151c1398b30e5c5d18f4ce", -"bigquery": "73151c1398b30e5c5d18f4ce", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "73151c1398b30e5c5d18f4ce", "duckdb": "73151c1398b30e5c5d18f4ce", "generic": "73151c1398b30e5c5d18f4ce", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73151c1398b30e5c5d18f4ce", -"postgres": "73151c1398b30e5c5d18f4ce", -"postgresql": "73151c1398b30e5c5d18f4ce", +"motherduck": "73151c1398b30e5c5d18f4ce", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f68b7dbd74ec7c3b9f3066ef", +"postgresql": "f68b7dbd74ec7c3b9f3066ef", "snowflake": "73151c1398b30e5c5d18f4ce", "tsql": "73151c1398b30e5c5d18f4ce" } @@ -9239,8 +9239,8 @@ "databricks": "4fb572024031cb7631777be1", "duckdb": "792f7d3389f076f50c713946", "generic": "792f7d3389f076f50c713946", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "792f7d3389f076f50c713946", +"motherduck": "792f7d3389f076f50c713946", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "792f7d3389f076f50c713946", "postgresql": "792f7d3389f076f50c713946", "snowflake": "792f7d3389f076f50c713946", @@ -9256,8 +9256,8 @@ "databricks": "236311169ac0526becd627ac", "duckdb": "236311169ac0526becd627ac", "generic": "236311169ac0526becd627ac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "236311169ac0526becd627ac", +"motherduck": "236311169ac0526becd627ac", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "236311169ac0526becd627ac", "postgresql": "236311169ac0526becd627ac", "snowflake": "236311169ac0526becd627ac", @@ -9273,10 +9273,10 @@ "databricks": "336fd1b7a1f7d8105c29280c", "duckdb": "0baaca265927140414f32b37", "generic": "336fd1b7a1f7d8105c29280c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "336fd1b7a1f7d8105c29280c", -"postgres": "336fd1b7a1f7d8105c29280c", -"postgresql": "336fd1b7a1f7d8105c29280c", +"motherduck": "0baaca265927140414f32b37", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9ea4932505663b43f527adad", +"postgresql": "9ea4932505663b43f527adad", "snowflake": "336fd1b7a1f7d8105c29280c", "tsql": "336fd1b7a1f7d8105c29280c" } @@ -9290,8 +9290,8 @@ "databricks": "806dd59533345612c07a861b", "duckdb": "806dd59533345612c07a861b", "generic": "806dd59533345612c07a861b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "806dd59533345612c07a861b", +"motherduck": "806dd59533345612c07a861b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "806dd59533345612c07a861b", "postgresql": "806dd59533345612c07a861b", "snowflake": "806dd59533345612c07a861b", @@ -9302,17 +9302,17 @@ "type", { "DuckDB": "647ac9829e390656c0783aba", -"None": "1404468a845477f54df838fc", +"None": "2df0f0517ee87dda5683d2bd", "bigquery": "2df0f0517ee87dda5683d2bd", "databricks": "2df0f0517ee87dda5683d2bd", "duckdb": "647ac9829e390656c0783aba", -"generic": "1404468a845477f54df838fc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2df0f0517ee87dda5683d2bd", -"postgres": "ee5dc4b764837cf002345fb9", -"postgresql": "ee5dc4b764837cf002345fb9", +"generic": "2df0f0517ee87dda5683d2bd", +"motherduck": "647ac9829e390656c0783aba", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2df0f0517ee87dda5683d2bd", +"postgresql": "2df0f0517ee87dda5683d2bd", "snowflake": "8d1f13df6b799b7b301d2e69", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"tsql": "2df0f0517ee87dda5683d2bd" } ], [ @@ -9324,8 +9324,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -9336,15 +9336,15 @@ "type", { "DuckDB": "6247229b9f890f25e7b3f0c5", -"None": "eb8ea3778b2debde09f2afb1", -"bigquery": "135a912ea74f76a35d9d0632", -"databricks": "eb8ea3778b2debde09f2afb1", +"None": "3fd7037beaaa396764e49e8b", +"bigquery": "3fd7037beaaa396764e49e8b", +"databricks": "3fd7037beaaa396764e49e8b", "duckdb": "6247229b9f890f25e7b3f0c5", -"generic": "eb8ea3778b2debde09f2afb1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "6247229b9f890f25e7b3f0c5", -"postgresql": "6247229b9f890f25e7b3f0c5", +"generic": "3fd7037beaaa396764e49e8b", +"motherduck": "6247229b9f890f25e7b3f0c5", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3fd7037beaaa396764e49e8b", +"postgresql": "3fd7037beaaa396764e49e8b", "snowflake": "8f60ea843371b982d069e65b", "tsql": "3fd7037beaaa396764e49e8b" } @@ -9358,8 +9358,8 @@ "databricks": "a57535ee64678e69d1acd48a", "duckdb": "077cc38442b3b9eae7bb07d0", "generic": "a57535ee64678e69d1acd48a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "077cc38442b3b9eae7bb07d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "077cc38442b3b9eae7bb07d0", "postgresql": "077cc38442b3b9eae7bb07d0", "snowflake": "befa13a14d1144835e866a4d", @@ -9375,8 +9375,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -9392,8 +9392,8 @@ "databricks": "ff8f9d6dbbdd5b3cf2e7c198", "duckdb": "ff8f9d6dbbdd5b3cf2e7c198", "generic": "ff8f9d6dbbdd5b3cf2e7c198", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ff8f9d6dbbdd5b3cf2e7c198", +"motherduck": "ff8f9d6dbbdd5b3cf2e7c198", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ff8f9d6dbbdd5b3cf2e7c198", "postgresql": "ff8f9d6dbbdd5b3cf2e7c198", "snowflake": "ff8f9d6dbbdd5b3cf2e7c198", @@ -9405,14 +9405,14 @@ { "DuckDB": "4928771fd9e503a30457d160", "None": "4928771fd9e503a30457d160", -"bigquery": "4928771fd9e503a30457d160", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "4928771fd9e503a30457d160", "duckdb": "4928771fd9e503a30457d160", "generic": "4928771fd9e503a30457d160", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4928771fd9e503a30457d160", -"postgres": "4928771fd9e503a30457d160", -"postgresql": "4928771fd9e503a30457d160", +"motherduck": "4928771fd9e503a30457d160", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "50ddfb168c08b09892261e82", +"postgresql": "50ddfb168c08b09892261e82", "snowflake": "4928771fd9e503a30457d160", "tsql": "4928771fd9e503a30457d160" } @@ -9426,10 +9426,10 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "b8321619b794db091c9e96a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b8321619b794db091c9e96a1", -"postgres": "b8321619b794db091c9e96a1", -"postgresql": "b8321619b794db091c9e96a1", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bce26544873d6ec9191c9a86", +"postgresql": "bce26544873d6ec9191c9a86", "snowflake": "b8321619b794db091c9e96a1", "tsql": "b8321619b794db091c9e96a1" } @@ -9440,11 +9440,11 @@ "DuckDB": "a0e5b4ce93e9317c3cea1cbf", "None": "a0e5b4ce93e9317c3cea1cbf", "bigquery": "a0e5b4ce93e9317c3cea1cbf", -"databricks": "c9d23fd01aedfa8b27cf08a6", +"databricks": "a0e5b4ce93e9317c3cea1cbf", "duckdb": "a0e5b4ce93e9317c3cea1cbf", "generic": "a0e5b4ce93e9317c3cea1cbf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c8a1fc1b91adcea5a885afe3", +"motherduck": "a0e5b4ce93e9317c3cea1cbf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a0e5b4ce93e9317c3cea1cbf", "postgresql": "a0e5b4ce93e9317c3cea1cbf", "snowflake": "a0e5b4ce93e9317c3cea1cbf", @@ -9460,8 +9460,8 @@ "databricks": "78de20f17ea5d66780a9f32e", "duckdb": "78de20f17ea5d66780a9f32e", "generic": "78de20f17ea5d66780a9f32e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "78de20f17ea5d66780a9f32e", +"motherduck": "78de20f17ea5d66780a9f32e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78de20f17ea5d66780a9f32e", "postgresql": "78de20f17ea5d66780a9f32e", "snowflake": "78de20f17ea5d66780a9f32e", @@ -9477,8 +9477,8 @@ "databricks": "40f264d27364684659f815eb", "duckdb": "8bb8eb3ad4ee794b787a664d", "generic": "8bb8eb3ad4ee794b787a664d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8bb8eb3ad4ee794b787a664d", +"motherduck": "8bb8eb3ad4ee794b787a664d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8bb8eb3ad4ee794b787a664d", "postgresql": "8bb8eb3ad4ee794b787a664d", "snowflake": "469cddcff389dac525f4efdf", @@ -9490,14 +9490,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -9511,10 +9511,10 @@ "databricks": "3673b4fbbd8a7fed7120ec2b", "duckdb": "3673b4fbbd8a7fed7120ec2b", "generic": "3673b4fbbd8a7fed7120ec2b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3673b4fbbd8a7fed7120ec2b", -"postgres": "3673b4fbbd8a7fed7120ec2b", -"postgresql": "3673b4fbbd8a7fed7120ec2b", +"motherduck": "3673b4fbbd8a7fed7120ec2b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9aff7fcc7d550e1a8d105471", +"postgresql": "9aff7fcc7d550e1a8d105471", "snowflake": "3673b4fbbd8a7fed7120ec2b", "tsql": "3673b4fbbd8a7fed7120ec2b" } @@ -9523,17 +9523,17 @@ "type", { "DuckDB": "a3b6b25265be330ec52a3322", -"None": "a3b6b25265be330ec52a3322", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "e3bc1b8bad22d8104417b00f", +"None": "62368fc15cefd5807807b209", +"bigquery": "c50a316d091c821866e4bfba", +"databricks": "62368fc15cefd5807807b209", "duckdb": "a3b6b25265be330ec52a3322", -"generic": "a3b6b25265be330ec52a3322", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "919b7ec6a7b49d8527b784cd", -"postgres": "a3b6b25265be330ec52a3322", -"postgresql": "a3b6b25265be330ec52a3322", +"generic": "62368fc15cefd5807807b209", +"motherduck": "a3b6b25265be330ec52a3322", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62368fc15cefd5807807b209", +"postgresql": "62368fc15cefd5807807b209", "snowflake": "919b7ec6a7b49d8527b784cd", -"tsql": "a3b6b25265be330ec52a3322" +"tsql": "62368fc15cefd5807807b209" } ], [ @@ -9541,14 +9541,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -9562,8 +9562,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -9579,8 +9579,8 @@ "databricks": "0b8ad171901cc2b7725617da", "duckdb": "0b8ad171901cc2b7725617da", "generic": "0b8ad171901cc2b7725617da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0b8ad171901cc2b7725617da", +"motherduck": "0b8ad171901cc2b7725617da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0b8ad171901cc2b7725617da", "postgresql": "0b8ad171901cc2b7725617da", "snowflake": "0b8ad171901cc2b7725617da", @@ -9596,8 +9596,8 @@ "databricks": "97dfbcc6528a197a87add69e", "duckdb": "208eafc9a5aab337867bb773", "generic": "97dfbcc6528a197a87add69e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "208eafc9a5aab337867bb773", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ca3d3fa7d8eb1608bd8752de", "postgresql": "ca3d3fa7d8eb1608bd8752de", "snowflake": "46250b305f6ac5a95e4d3083", @@ -9613,8 +9613,8 @@ "databricks": "c4d601bdc1b1f357b3e9bbdf", "duckdb": "c4d601bdc1b1f357b3e9bbdf", "generic": "c4d601bdc1b1f357b3e9bbdf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c4d601bdc1b1f357b3e9bbdf", +"motherduck": "c4d601bdc1b1f357b3e9bbdf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c4d601bdc1b1f357b3e9bbdf", "postgresql": "c4d601bdc1b1f357b3e9bbdf", "snowflake": "c4d601bdc1b1f357b3e9bbdf", @@ -9630,10 +9630,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -9647,8 +9647,8 @@ "databricks": "e7c121d9cbaf6e6b91cfdf74", "duckdb": "e7c121d9cbaf6e6b91cfdf74", "generic": "e7c121d9cbaf6e6b91cfdf74", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7c121d9cbaf6e6b91cfdf74", +"motherduck": "e7c121d9cbaf6e6b91cfdf74", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7c121d9cbaf6e6b91cfdf74", "postgresql": "e7c121d9cbaf6e6b91cfdf74", "snowflake": "e7c121d9cbaf6e6b91cfdf74", @@ -9664,8 +9664,8 @@ "databricks": "962ea05bab624c1f8c259a65", "duckdb": "962ea05bab624c1f8c259a65", "generic": "962ea05bab624c1f8c259a65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "962ea05bab624c1f8c259a65", +"motherduck": "962ea05bab624c1f8c259a65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "962ea05bab624c1f8c259a65", "postgresql": "962ea05bab624c1f8c259a65", "snowflake": "962ea05bab624c1f8c259a65", @@ -9677,12 +9677,12 @@ { "DuckDB": "3e888d249494102b9ba328b2", "None": "3e888d249494102b9ba328b2", -"bigquery": "3e888d249494102b9ba328b2", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "3e888d249494102b9ba328b2", "duckdb": "3e888d249494102b9ba328b2", "generic": "3e888d249494102b9ba328b2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3e888d249494102b9ba328b2", +"motherduck": "3e888d249494102b9ba328b2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3e888d249494102b9ba328b2", "postgresql": "3e888d249494102b9ba328b2", "snowflake": "3e888d249494102b9ba328b2", @@ -9698,8 +9698,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -9715,8 +9715,8 @@ "databricks": "652ff8b21f638eac8fdc46c5", "duckdb": "652ff8b21f638eac8fdc46c5", "generic": "652ff8b21f638eac8fdc46c5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "652ff8b21f638eac8fdc46c5", +"motherduck": "652ff8b21f638eac8fdc46c5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "652ff8b21f638eac8fdc46c5", "postgresql": "652ff8b21f638eac8fdc46c5", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -9728,12 +9728,12 @@ { "DuckDB": "0989b67047ee9c0a1d9f2cf4", "None": "0989b67047ee9c0a1d9f2cf4", -"bigquery": "0989b67047ee9c0a1d9f2cf4", +"bigquery": "e1c1429838b93039324a47f3", "databricks": "0989b67047ee9c0a1d9f2cf4", "duckdb": "0989b67047ee9c0a1d9f2cf4", "generic": "0989b67047ee9c0a1d9f2cf4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0989b67047ee9c0a1d9f2cf4", +"motherduck": "0989b67047ee9c0a1d9f2cf4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0989b67047ee9c0a1d9f2cf4", "postgresql": "0989b67047ee9c0a1d9f2cf4", "snowflake": "0989b67047ee9c0a1d9f2cf4", @@ -9749,8 +9749,8 @@ "databricks": "1275b5b0c52f69cf592c004b", "duckdb": "1275b5b0c52f69cf592c004b", "generic": "1275b5b0c52f69cf592c004b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1275b5b0c52f69cf592c004b", +"motherduck": "1275b5b0c52f69cf592c004b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1275b5b0c52f69cf592c004b", "postgresql": "1275b5b0c52f69cf592c004b", "snowflake": "1275b5b0c52f69cf592c004b", @@ -9762,14 +9762,14 @@ { "DuckDB": "3e611643f0beab4953c4e09b", "None": "3e611643f0beab4953c4e09b", -"bigquery": "2a0918be53051fe629d9ff8f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "3e611643f0beab4953c4e09b", "duckdb": "3e611643f0beab4953c4e09b", "generic": "3e611643f0beab4953c4e09b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3e611643f0beab4953c4e09b", -"postgres": "3e611643f0beab4953c4e09b", -"postgresql": "3e611643f0beab4953c4e09b", +"motherduck": "3e611643f0beab4953c4e09b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cae477ce59c72869f37a9a40", +"postgresql": "cae477ce59c72869f37a9a40", "snowflake": "3e611643f0beab4953c4e09b", "tsql": "3e611643f0beab4953c4e09b" } @@ -9779,14 +9779,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -9800,8 +9800,8 @@ "databricks": "70fb4728c3adcf6dbe4975d5", "duckdb": "70fb4728c3adcf6dbe4975d5", "generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", @@ -9817,10 +9817,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -9830,14 +9830,14 @@ { "DuckDB": "b6b82efd03def9465e4f97b2", "None": "b6b82efd03def9465e4f97b2", -"bigquery": "b6b82efd03def9465e4f97b2", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "b6b82efd03def9465e4f97b2", "duckdb": "b6b82efd03def9465e4f97b2", "generic": "b6b82efd03def9465e4f97b2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b6b82efd03def9465e4f97b2", -"postgres": "b6b82efd03def9465e4f97b2", -"postgresql": "b6b82efd03def9465e4f97b2", +"motherduck": "b6b82efd03def9465e4f97b2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a3923c3d2c49043629c52bf6", +"postgresql": "a3923c3d2c49043629c52bf6", "snowflake": "b6b82efd03def9465e4f97b2", "tsql": "b6b82efd03def9465e4f97b2" } @@ -9851,10 +9851,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -9868,10 +9868,10 @@ "databricks": "80c23e10e486925cf3b33f9d", "duckdb": "80c23e10e486925cf3b33f9d", "generic": "80c23e10e486925cf3b33f9d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "80c23e10e486925cf3b33f9d", -"postgres": "80c23e10e486925cf3b33f9d", -"postgresql": "80c23e10e486925cf3b33f9d", +"motherduck": "80c23e10e486925cf3b33f9d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "30466b1eff038acb4463e46d", +"postgresql": "30466b1eff038acb4463e46d", "snowflake": "80c23e10e486925cf3b33f9d", "tsql": "80c23e10e486925cf3b33f9d" } @@ -9885,8 +9885,8 @@ "databricks": "60a23b7dcae6c79f4de83068", "duckdb": "60a23b7dcae6c79f4de83068", "generic": "60a23b7dcae6c79f4de83068", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60a23b7dcae6c79f4de83068", +"motherduck": "60a23b7dcae6c79f4de83068", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "60a23b7dcae6c79f4de83068", "postgresql": "60a23b7dcae6c79f4de83068", "snowflake": "60a23b7dcae6c79f4de83068", @@ -9902,8 +9902,8 @@ "databricks": "f4c43a87a78970bca1f88b09", "duckdb": "f4c43a87a78970bca1f88b09", "generic": "f4c43a87a78970bca1f88b09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4c43a87a78970bca1f88b09", +"motherduck": "f4c43a87a78970bca1f88b09", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c43a87a78970bca1f88b09", "postgresql": "f4c43a87a78970bca1f88b09", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -9919,8 +9919,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -9932,14 +9932,14 @@ { "DuckDB": "c4346d0257336219f1d26890", "None": "c4346d0257336219f1d26890", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "c4346d0257336219f1d26890", "duckdb": "c4346d0257336219f1d26890", "generic": "c4346d0257336219f1d26890", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c4346d0257336219f1d26890", -"postgres": "c4346d0257336219f1d26890", -"postgresql": "c4346d0257336219f1d26890", +"motherduck": "c4346d0257336219f1d26890", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "19bdd92ea0cc5fb028657510", +"postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "4e24a80b20b9dee0cbbfac17", "tsql": "c4346d0257336219f1d26890" } @@ -9953,8 +9953,8 @@ "databricks": "cabeb00c4bc6c9e3d88a79ed", "duckdb": "bcc4b2c4a8f93d406cf00bc8", "generic": "cabeb00c4bc6c9e3d88a79ed", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "bcc4b2c4a8f93d406cf00bc8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "bcc4b2c4a8f93d406cf00bc8", "postgresql": "bcc4b2c4a8f93d406cf00bc8", "snowflake": "c4046288c6fec9a1344b0faa", @@ -9970,8 +9970,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -9983,14 +9983,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -10004,10 +10004,10 @@ "databricks": "d227b99a82e505d599c2ed8a", "duckdb": "35a9c5c1419bff507cf65ae2", "generic": "d227b99a82e505d599c2ed8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d227b99a82e505d599c2ed8a", -"postgres": "d227b99a82e505d599c2ed8a", -"postgresql": "d227b99a82e505d599c2ed8a", +"motherduck": "35a9c5c1419bff507cf65ae2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "35a9c5c1419bff507cf65ae2", +"postgresql": "35a9c5c1419bff507cf65ae2", "snowflake": "d227b99a82e505d599c2ed8a", "tsql": "d227b99a82e505d599c2ed8a" } @@ -10021,8 +10021,8 @@ "databricks": "fa07c8ec6ebf3228010ebd9a", "duckdb": "fa07c8ec6ebf3228010ebd9a", "generic": "fa07c8ec6ebf3228010ebd9a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fa07c8ec6ebf3228010ebd9a", +"motherduck": "fa07c8ec6ebf3228010ebd9a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fa07c8ec6ebf3228010ebd9a", "postgresql": "fa07c8ec6ebf3228010ebd9a", "snowflake": "fa07c8ec6ebf3228010ebd9a", @@ -10038,8 +10038,8 @@ "databricks": "ab5ad6180652b3b00d1f5b01", "duckdb": "ab5ad6180652b3b00d1f5b01", "generic": "ab5ad6180652b3b00d1f5b01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab5ad6180652b3b00d1f5b01", +"motherduck": "ab5ad6180652b3b00d1f5b01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab5ad6180652b3b00d1f5b01", "postgresql": "ab5ad6180652b3b00d1f5b01", "snowflake": "226beccc1d430773f4e67b70", @@ -10050,17 +10050,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -10072,10 +10072,10 @@ "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "35e216b6921bf843994f2eea", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "35e216b6921bf843994f2eea", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -10089,8 +10089,8 @@ "databricks": "c2700ca3573352821e09af03", "duckdb": "c2700ca3573352821e09af03", "generic": "c2700ca3573352821e09af03", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c2700ca3573352821e09af03", +"motherduck": "c2700ca3573352821e09af03", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c2700ca3573352821e09af03", "postgresql": "c2700ca3573352821e09af03", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -10106,8 +10106,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -10123,8 +10123,8 @@ "databricks": "4b27937f44f966ff9f3a7c76", "duckdb": "4b27937f44f966ff9f3a7c76", "generic": "4b27937f44f966ff9f3a7c76", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b27937f44f966ff9f3a7c76", +"motherduck": "4b27937f44f966ff9f3a7c76", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4b27937f44f966ff9f3a7c76", "postgresql": "4b27937f44f966ff9f3a7c76", "snowflake": "4b27937f44f966ff9f3a7c76", @@ -10140,8 +10140,8 @@ "databricks": "d63e1b6839b94c8b42376477", "duckdb": "d63e1b6839b94c8b42376477", "generic": "d63e1b6839b94c8b42376477", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d63e1b6839b94c8b42376477", +"motherduck": "d63e1b6839b94c8b42376477", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d63e1b6839b94c8b42376477", "postgresql": "d63e1b6839b94c8b42376477", "snowflake": "d63e1b6839b94c8b42376477", @@ -10157,8 +10157,8 @@ "databricks": "c752fd9fe2ad3b86b8b899da", "duckdb": "c752fd9fe2ad3b86b8b899da", "generic": "c752fd9fe2ad3b86b8b899da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c752fd9fe2ad3b86b8b899da", +"motherduck": "c752fd9fe2ad3b86b8b899da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -10174,8 +10174,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "1e75e5227bea0ae294ac7af1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19bdd92ea0cc5fb028657510", "postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -10191,8 +10191,8 @@ "databricks": "57922064a7dc546cbb50e42b", "duckdb": "57922064a7dc546cbb50e42b", "generic": "57922064a7dc546cbb50e42b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "57922064a7dc546cbb50e42b", +"motherduck": "57922064a7dc546cbb50e42b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "57922064a7dc546cbb50e42b", "postgresql": "57922064a7dc546cbb50e42b", "snowflake": "57922064a7dc546cbb50e42b", @@ -10208,8 +10208,8 @@ "databricks": "d4a375fcf7c3190951992469", "duckdb": "d4a375fcf7c3190951992469", "generic": "d4a375fcf7c3190951992469", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d4a375fcf7c3190951992469", +"motherduck": "d4a375fcf7c3190951992469", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d4a375fcf7c3190951992469", "postgresql": "d4a375fcf7c3190951992469", "snowflake": "6306b9e88f7b13dbb6a32c00", @@ -10219,16 +10219,16 @@ [ "type", { -"DuckDB": "8698d724534891869075175e", -"None": "0017a88bb60ec3403c786ed8", +"DuckDB": "aa7afbd57560be67e5221a3b", +"None": "47e0816fecdd9e015d7aeb79", "bigquery": "47e0816fecdd9e015d7aeb79", -"databricks": "0017a88bb60ec3403c786ed8", -"duckdb": "8698d724534891869075175e", -"generic": "0017a88bb60ec3403c786ed8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "8698d724534891869075175e", -"postgresql": "8698d724534891869075175e", +"databricks": "47e0816fecdd9e015d7aeb79", +"duckdb": "aa7afbd57560be67e5221a3b", +"generic": "47e0816fecdd9e015d7aeb79", +"motherduck": "aa7afbd57560be67e5221a3b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "aa7afbd57560be67e5221a3b", +"postgresql": "aa7afbd57560be67e5221a3b", "snowflake": "e6f36041b1d9fb8fe3a18cbb", "tsql": "364031fff119e6530478e415" } @@ -10242,8 +10242,8 @@ "databricks": "14c32cfb5e5135338d015f42", "duckdb": "14c32cfb5e5135338d015f42", "generic": "14c32cfb5e5135338d015f42", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "14c32cfb5e5135338d015f42", +"motherduck": "14c32cfb5e5135338d015f42", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "14c32cfb5e5135338d015f42", "postgresql": "14c32cfb5e5135338d015f42", "snowflake": "14c32cfb5e5135338d015f42", @@ -10259,8 +10259,8 @@ "databricks": "73d31c6e78cdc3b595b9c4e9", "duckdb": "73d31c6e78cdc3b595b9c4e9", "generic": "73d31c6e78cdc3b595b9c4e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73d31c6e78cdc3b595b9c4e9", +"motherduck": "73d31c6e78cdc3b595b9c4e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "73d31c6e78cdc3b595b9c4e9", "postgresql": "73d31c6e78cdc3b595b9c4e9", "snowflake": "73d31c6e78cdc3b595b9c4e9", @@ -10276,10 +10276,10 @@ "databricks": "6f763a99e9c69109526b40c0", "duckdb": "6f763a99e9c69109526b40c0", "generic": "6f763a99e9c69109526b40c0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6f763a99e9c69109526b40c0", -"postgres": "6f763a99e9c69109526b40c0", -"postgresql": "6f763a99e9c69109526b40c0", +"motherduck": "6f763a99e9c69109526b40c0", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5226d7e2a62c7650a87d6e84", +"postgresql": "5226d7e2a62c7650a87d6e84", "snowflake": "2705f19942a4194a72576911", "tsql": "6f763a99e9c69109526b40c0" } @@ -10293,8 +10293,8 @@ "databricks": "78b2cf275c640af54feeca31", "duckdb": "78b2cf275c640af54feeca31", "generic": "78b2cf275c640af54feeca31", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "78b2cf275c640af54feeca31", +"motherduck": "78b2cf275c640af54feeca31", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78b2cf275c640af54feeca31", "postgresql": "78b2cf275c640af54feeca31", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -10310,10 +10310,10 @@ "databricks": "f78e07518606fbc42e0f28fe", "duckdb": "f78e07518606fbc42e0f28fe", "generic": "f78e07518606fbc42e0f28fe", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f78e07518606fbc42e0f28fe", -"postgres": "f78e07518606fbc42e0f28fe", -"postgresql": "f78e07518606fbc42e0f28fe", +"motherduck": "f78e07518606fbc42e0f28fe", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "726bc47de786f118c37530f7", +"postgresql": "726bc47de786f118c37530f7", "snowflake": "f78e07518606fbc42e0f28fe", "tsql": "f78e07518606fbc42e0f28fe" } @@ -10327,8 +10327,8 @@ "databricks": "11f5a5e0f332f3a87c8ff396", "duckdb": "11f5a5e0f332f3a87c8ff396", "generic": "11f5a5e0f332f3a87c8ff396", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "11f5a5e0f332f3a87c8ff396", +"motherduck": "11f5a5e0f332f3a87c8ff396", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "11f5a5e0f332f3a87c8ff396", "postgresql": "11f5a5e0f332f3a87c8ff396", "snowflake": "11f5a5e0f332f3a87c8ff396", @@ -10344,8 +10344,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -10355,18 +10355,18 @@ [ "type", { -"DuckDB": "f20ec02865769c13aa2d2b67", -"None": "f20ec02865769c13aa2d2b67", -"bigquery": "8575088c2f44d4349e1307a4", -"databricks": "e40ec5056d50c25089ee3934", -"duckdb": "f20ec02865769c13aa2d2b67", -"generic": "f20ec02865769c13aa2d2b67", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f20ec02865769c13aa2d2b67", -"postgres": "f20ec02865769c13aa2d2b67", -"postgresql": "f20ec02865769c13aa2d2b67", -"snowflake": "e5502f3bf9aad4d98960c7b4", -"tsql": "f20ec02865769c13aa2d2b67" +"DuckDB": "143fb490be175b5b233c87a4", +"None": "143fb490be175b5b233c87a4", +"bigquery": "5391753213125132f9ba8258", +"databricks": "e95f9763c510e80ddb1422a0", +"duckdb": "143fb490be175b5b233c87a4", +"generic": "143fb490be175b5b233c87a4", +"motherduck": "143fb490be175b5b233c87a4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "143fb490be175b5b233c87a4", +"postgresql": "143fb490be175b5b233c87a4", +"snowflake": "e6d0f723aa7559449fb75d84", +"tsql": "143fb490be175b5b233c87a4" } ], [ @@ -10378,8 +10378,8 @@ "databricks": "d22967425ad0e5a048596ac6", "duckdb": "d22967425ad0e5a048596ac6", "generic": "d22967425ad0e5a048596ac6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d22967425ad0e5a048596ac6", +"motherduck": "d22967425ad0e5a048596ac6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d22967425ad0e5a048596ac6", "postgresql": "d22967425ad0e5a048596ac6", "snowflake": "d22967425ad0e5a048596ac6", @@ -10395,8 +10395,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -10412,8 +10412,8 @@ "databricks": "eded69a48ff8476dbf73ff05", "duckdb": "eded69a48ff8476dbf73ff05", "generic": "eded69a48ff8476dbf73ff05", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eded69a48ff8476dbf73ff05", +"motherduck": "eded69a48ff8476dbf73ff05", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eded69a48ff8476dbf73ff05", "postgresql": "eded69a48ff8476dbf73ff05", "snowflake": "eded69a48ff8476dbf73ff05", @@ -10429,10 +10429,10 @@ "databricks": "d7bedd3c7663f140f4d10bd4", "duckdb": "d7bedd3c7663f140f4d10bd4", "generic": "d7bedd3c7663f140f4d10bd4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d7bedd3c7663f140f4d10bd4", -"postgres": "d7bedd3c7663f140f4d10bd4", -"postgresql": "d7bedd3c7663f140f4d10bd4", +"motherduck": "d7bedd3c7663f140f4d10bd4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "d7e01d31db59d1d0507cd75c", +"postgresql": "d7e01d31db59d1d0507cd75c", "snowflake": "d7bedd3c7663f140f4d10bd4", "tsql": "d7bedd3c7663f140f4d10bd4" } @@ -10442,14 +10442,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -10463,8 +10463,8 @@ "databricks": "c82815abe30b3d099903f838", "duckdb": "c82815abe30b3d099903f838", "generic": "c82815abe30b3d099903f838", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c82815abe30b3d099903f838", +"motherduck": "c82815abe30b3d099903f838", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c82815abe30b3d099903f838", "postgresql": "c82815abe30b3d099903f838", "snowflake": "c82815abe30b3d099903f838", @@ -10480,8 +10480,8 @@ "databricks": "3739567e772c71d3b587ca23", "duckdb": "3739567e772c71d3b587ca23", "generic": "3739567e772c71d3b587ca23", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3739567e772c71d3b587ca23", +"motherduck": "3739567e772c71d3b587ca23", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3739567e772c71d3b587ca23", "postgresql": "3739567e772c71d3b587ca23", "snowflake": "3739567e772c71d3b587ca23", @@ -10497,8 +10497,8 @@ "databricks": "83fdbd1f0a6ee4e72faa4d94", "duckdb": "83fdbd1f0a6ee4e72faa4d94", "generic": "83fdbd1f0a6ee4e72faa4d94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "83fdbd1f0a6ee4e72faa4d94", +"motherduck": "83fdbd1f0a6ee4e72faa4d94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "83fdbd1f0a6ee4e72faa4d94", "postgresql": "83fdbd1f0a6ee4e72faa4d94", "snowflake": "83fdbd1f0a6ee4e72faa4d94", @@ -10510,12 +10510,12 @@ { "DuckDB": "af57dd2575fa5a01917ec9cf", "None": "af57dd2575fa5a01917ec9cf", -"bigquery": "af57dd2575fa5a01917ec9cf", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "af57dd2575fa5a01917ec9cf", "duckdb": "af57dd2575fa5a01917ec9cf", "generic": "af57dd2575fa5a01917ec9cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "af57dd2575fa5a01917ec9cf", +"motherduck": "af57dd2575fa5a01917ec9cf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "af57dd2575fa5a01917ec9cf", "postgresql": "af57dd2575fa5a01917ec9cf", "snowflake": "af57dd2575fa5a01917ec9cf", @@ -10531,8 +10531,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -10548,8 +10548,8 @@ "databricks": "d3bcf10e00645b12a802c7d0", "duckdb": "c0eec31620dfc2ac0028173d", "generic": "d3bcf10e00645b12a802c7d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "c0eec31620dfc2ac0028173d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c0eec31620dfc2ac0028173d", "postgresql": "c0eec31620dfc2ac0028173d", "snowflake": "d379c0ee4dad89e570b7329e", @@ -10565,8 +10565,8 @@ "databricks": "39257e40f37cf05c780a4e22", "duckdb": "39257e40f37cf05c780a4e22", "generic": "39257e40f37cf05c780a4e22", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "39257e40f37cf05c780a4e22", +"motherduck": "39257e40f37cf05c780a4e22", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "39257e40f37cf05c780a4e22", "postgresql": "39257e40f37cf05c780a4e22", "snowflake": "39257e40f37cf05c780a4e22", @@ -10578,14 +10578,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -10599,8 +10599,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "251aba3fef503ce64556c701", "postgresql": "251aba3fef503ce64556c701", "snowflake": "c50a316d091c821866e4bfba", @@ -10612,12 +10612,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -10633,8 +10633,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -10650,8 +10650,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "251aba3fef503ce64556c701", "postgresql": "251aba3fef503ce64556c701", "snowflake": "c50a316d091c821866e4bfba", @@ -10667,8 +10667,8 @@ "databricks": "1bdf5908ca249b2331a0427c", "duckdb": "1bdf5908ca249b2331a0427c", "generic": "1bdf5908ca249b2331a0427c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1bdf5908ca249b2331a0427c", +"motherduck": "1bdf5908ca249b2331a0427c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1bdf5908ca249b2331a0427c", "postgresql": "1bdf5908ca249b2331a0427c", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -10684,8 +10684,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -10701,8 +10701,8 @@ "databricks": "53ec6db7b2c81e2ceee285a3", "duckdb": "53ec6db7b2c81e2ceee285a3", "generic": "53ec6db7b2c81e2ceee285a3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "53ec6db7b2c81e2ceee285a3", +"motherduck": "53ec6db7b2c81e2ceee285a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "53ec6db7b2c81e2ceee285a3", "postgresql": "53ec6db7b2c81e2ceee285a3", "snowflake": "53ec6db7b2c81e2ceee285a3", @@ -10713,17 +10713,17 @@ "type", { "DuckDB": "5abf4ec799a2ec32fb10da7e", -"None": "5abf4ec799a2ec32fb10da7e", -"bigquery": "5abf4ec799a2ec32fb10da7e", -"databricks": "5abf4ec799a2ec32fb10da7e", +"None": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", +"databricks": "4b356542044f1267fcfc487a", "duckdb": "5abf4ec799a2ec32fb10da7e", -"generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", -"postgres": "5abf4ec799a2ec32fb10da7e", -"postgresql": "5abf4ec799a2ec32fb10da7e", +"generic": "4b356542044f1267fcfc487a", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "a5656d70e208254d1d3e9b4f", -"tsql": "5abf4ec799a2ec32fb10da7e" +"tsql": "4b356542044f1267fcfc487a" } ], [ @@ -10731,12 +10731,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -10752,8 +10752,8 @@ "databricks": "2427f5522575cdc3d6a74e4c", "duckdb": "2427f5522575cdc3d6a74e4c", "generic": "2427f5522575cdc3d6a74e4c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2427f5522575cdc3d6a74e4c", +"motherduck": "2427f5522575cdc3d6a74e4c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2427f5522575cdc3d6a74e4c", "postgresql": "2427f5522575cdc3d6a74e4c", "snowflake": "2427f5522575cdc3d6a74e4c", @@ -10765,14 +10765,14 @@ { "DuckDB": "158229f1c3f1085b80d3190e", "None": "158229f1c3f1085b80d3190e", -"bigquery": "158229f1c3f1085b80d3190e", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "158229f1c3f1085b80d3190e", "duckdb": "158229f1c3f1085b80d3190e", "generic": "158229f1c3f1085b80d3190e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "158229f1c3f1085b80d3190e", -"postgres": "158229f1c3f1085b80d3190e", -"postgresql": "158229f1c3f1085b80d3190e", +"motherduck": "158229f1c3f1085b80d3190e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7a1a9517a8cf96477b67c17e", +"postgresql": "7a1a9517a8cf96477b67c17e", "snowflake": "158229f1c3f1085b80d3190e", "tsql": "158229f1c3f1085b80d3190e" } @@ -10786,11 +10786,11 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "e3bc1b8bad22d8104417b00f", "generic": "e3bc1b8bad22d8104417b00f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e3bc1b8bad22d8104417b00f", -"postgres": "111af35848fe500ec2ae54b2", -"postgresql": "111af35848fe500ec2ae54b2", -"snowflake": "e3bc1b8bad22d8104417b00f", +"motherduck": "e3bc1b8bad22d8104417b00f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b7d6fc051a3c1ea6deb29ff4", +"postgresql": "b7d6fc051a3c1ea6deb29ff4", +"snowflake": "e3bc1b8bad22d8104417b00f", "tsql": "e3bc1b8bad22d8104417b00f" } ], @@ -10798,17 +10798,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -10816,14 +10816,14 @@ { "DuckDB": "9bb8e9bdc03c6262fc0b887e", "None": "9bb8e9bdc03c6262fc0b887e", -"bigquery": "9bb8e9bdc03c6262fc0b887e", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "9bb8e9bdc03c6262fc0b887e", "duckdb": "9bb8e9bdc03c6262fc0b887e", "generic": "9bb8e9bdc03c6262fc0b887e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9bb8e9bdc03c6262fc0b887e", -"postgres": "9bb8e9bdc03c6262fc0b887e", -"postgresql": "9bb8e9bdc03c6262fc0b887e", +"motherduck": "9bb8e9bdc03c6262fc0b887e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f2ce9dc78f83119cd19850e3", +"postgresql": "f2ce9dc78f83119cd19850e3", "snowflake": "9bb8e9bdc03c6262fc0b887e", "tsql": "9bb8e9bdc03c6262fc0b887e" } @@ -10832,17 +10832,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -10850,14 +10850,14 @@ { "DuckDB": "b69a57be16780e0a5e4acadb", "None": "b69a57be16780e0a5e4acadb", -"bigquery": "b69a57be16780e0a5e4acadb", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "b69a57be16780e0a5e4acadb", "duckdb": "b69a57be16780e0a5e4acadb", "generic": "b69a57be16780e0a5e4acadb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b69a57be16780e0a5e4acadb", -"postgres": "b69a57be16780e0a5e4acadb", -"postgresql": "b69a57be16780e0a5e4acadb", +"motherduck": "b69a57be16780e0a5e4acadb", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a7d1c1767c3d4e900c7d3cb4", +"postgresql": "a7d1c1767c3d4e900c7d3cb4", "snowflake": "b69a57be16780e0a5e4acadb", "tsql": "b69a57be16780e0a5e4acadb" } @@ -10871,8 +10871,8 @@ "databricks": "c703e456945a8f06d005c98b", "duckdb": "c703e456945a8f06d005c98b", "generic": "c703e456945a8f06d005c98b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c703e456945a8f06d005c98b", +"motherduck": "c703e456945a8f06d005c98b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c703e456945a8f06d005c98b", "postgresql": "c703e456945a8f06d005c98b", "snowflake": "c703e456945a8f06d005c98b", @@ -10888,8 +10888,8 @@ "databricks": "8371f7fd8d5afa0c7c86482b", "duckdb": "8371f7fd8d5afa0c7c86482b", "generic": "8371f7fd8d5afa0c7c86482b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8371f7fd8d5afa0c7c86482b", +"motherduck": "8371f7fd8d5afa0c7c86482b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8371f7fd8d5afa0c7c86482b", "postgresql": "8371f7fd8d5afa0c7c86482b", "snowflake": "8371f7fd8d5afa0c7c86482b", @@ -10905,10 +10905,10 @@ "databricks": "f4fc387af06ec14d0ed37c37", "duckdb": "f4fc387af06ec14d0ed37c37", "generic": "f4fc387af06ec14d0ed37c37", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4fc387af06ec14d0ed37c37", -"postgres": "f4fc387af06ec14d0ed37c37", -"postgresql": "f4fc387af06ec14d0ed37c37", +"motherduck": "f4fc387af06ec14d0ed37c37", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "6e561b85b14713ee50331da1", +"postgresql": "6e561b85b14713ee50331da1", "snowflake": "f4fc387af06ec14d0ed37c37", "tsql": "f4fc387af06ec14d0ed37c37" } @@ -10922,8 +10922,8 @@ "databricks": "97a92f4a47efe38678dac07f", "duckdb": "97a92f4a47efe38678dac07f", "generic": "97a92f4a47efe38678dac07f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "97a92f4a47efe38678dac07f", +"motherduck": "97a92f4a47efe38678dac07f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "97a92f4a47efe38678dac07f", "postgresql": "97a92f4a47efe38678dac07f", "snowflake": "97a92f4a47efe38678dac07f", @@ -10939,8 +10939,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -10956,8 +10956,8 @@ "databricks": "c6889f2965db73017be6f6e7", "duckdb": "c6889f2965db73017be6f6e7", "generic": "c6889f2965db73017be6f6e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6889f2965db73017be6f6e7", +"motherduck": "c6889f2965db73017be6f6e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6889f2965db73017be6f6e7", "postgresql": "c6889f2965db73017be6f6e7", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -10973,10 +10973,10 @@ "databricks": "279ea7f6f6a3910de04b795b", "duckdb": "279ea7f6f6a3910de04b795b", "generic": "279ea7f6f6a3910de04b795b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "279ea7f6f6a3910de04b795b", -"postgres": "279ea7f6f6a3910de04b795b", -"postgresql": "279ea7f6f6a3910de04b795b", +"motherduck": "279ea7f6f6a3910de04b795b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bc520aebd5872e40030ec7d9", +"postgresql": "bc520aebd5872e40030ec7d9", "snowflake": "279ea7f6f6a3910de04b795b", "tsql": "279ea7f6f6a3910de04b795b" } @@ -10986,12 +10986,12 @@ { "DuckDB": "04f0b885283ff5fc129874ad", "None": "04f0b885283ff5fc129874ad", -"bigquery": "04f0b885283ff5fc129874ad", +"bigquery": "e1c1429838b93039324a47f3", "databricks": "04f0b885283ff5fc129874ad", "duckdb": "04f0b885283ff5fc129874ad", "generic": "04f0b885283ff5fc129874ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "04f0b885283ff5fc129874ad", +"motherduck": "04f0b885283ff5fc129874ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "04f0b885283ff5fc129874ad", "postgresql": "04f0b885283ff5fc129874ad", "snowflake": "04f0b885283ff5fc129874ad", @@ -11001,14 +11001,14 @@ [ "type", { -"DuckDB": "c6654395c62d21b415299f72", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "c6654395c62d21b415299f72", "bigquery": "0712c33a016f7dd6a37ee011", "databricks": "c6654395c62d21b415299f72", -"duckdb": "c6654395c62d21b415299f72", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "c6654395c62d21b415299f72", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2978a8fe925c7f93c6e9fcfb", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6654395c62d21b415299f72", "postgresql": "c6654395c62d21b415299f72", "snowflake": "0712c33a016f7dd6a37ee011", @@ -11019,17 +11019,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -11041,8 +11041,8 @@ "databricks": "93c1b5b5960e26479baf88d0", "duckdb": "93c1b5b5960e26479baf88d0", "generic": "93c1b5b5960e26479baf88d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "93c1b5b5960e26479baf88d0", +"motherduck": "93c1b5b5960e26479baf88d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "93c1b5b5960e26479baf88d0", "postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "93c1b5b5960e26479baf88d0", @@ -11058,8 +11058,8 @@ "databricks": "6a912271e85e6d85c1238bc0", "duckdb": "6a912271e85e6d85c1238bc0", "generic": "6a912271e85e6d85c1238bc0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6a912271e85e6d85c1238bc0", +"motherduck": "6a912271e85e6d85c1238bc0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6a912271e85e6d85c1238bc0", "postgresql": "6a912271e85e6d85c1238bc0", "snowflake": "6a912271e85e6d85c1238bc0", @@ -11075,8 +11075,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -11092,10 +11092,10 @@ "databricks": "eea9a25a6fbf8e049a445513", "duckdb": "eea9a25a6fbf8e049a445513", "generic": "eea9a25a6fbf8e049a445513", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eea9a25a6fbf8e049a445513", -"postgres": "eea9a25a6fbf8e049a445513", -"postgresql": "eea9a25a6fbf8e049a445513", +"motherduck": "eea9a25a6fbf8e049a445513", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "769f762f4c7cffb2990ddacb", +"postgresql": "769f762f4c7cffb2990ddacb", "snowflake": "eea9a25a6fbf8e049a445513", "tsql": "eea9a25a6fbf8e049a445513" } @@ -11109,8 +11109,8 @@ "databricks": "304be1d51ad99bfab3750f57", "duckdb": "304be1d51ad99bfab3750f57", "generic": "304be1d51ad99bfab3750f57", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "304be1d51ad99bfab3750f57", +"motherduck": "304be1d51ad99bfab3750f57", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "304be1d51ad99bfab3750f57", "postgresql": "304be1d51ad99bfab3750f57", "snowflake": "304be1d51ad99bfab3750f57", @@ -11126,8 +11126,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -11143,8 +11143,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -11160,8 +11160,8 @@ "databricks": "5d0a19a893dbf0c1e6e9f15c", "duckdb": "5d0a19a893dbf0c1e6e9f15c", "generic": "5d0a19a893dbf0c1e6e9f15c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d0a19a893dbf0c1e6e9f15c", +"motherduck": "5d0a19a893dbf0c1e6e9f15c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5d0a19a893dbf0c1e6e9f15c", "postgresql": "5d0a19a893dbf0c1e6e9f15c", "snowflake": "5d0a19a893dbf0c1e6e9f15c", @@ -11173,14 +11173,14 @@ { "DuckDB": "9bb8e9bdc03c6262fc0b887e", "None": "9bb8e9bdc03c6262fc0b887e", -"bigquery": "9bb8e9bdc03c6262fc0b887e", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "9bb8e9bdc03c6262fc0b887e", "duckdb": "9bb8e9bdc03c6262fc0b887e", "generic": "9bb8e9bdc03c6262fc0b887e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9bb8e9bdc03c6262fc0b887e", -"postgres": "9bb8e9bdc03c6262fc0b887e", -"postgresql": "9bb8e9bdc03c6262fc0b887e", +"motherduck": "9bb8e9bdc03c6262fc0b887e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f2ce9dc78f83119cd19850e3", +"postgresql": "f2ce9dc78f83119cd19850e3", "snowflake": "9bb8e9bdc03c6262fc0b887e", "tsql": "9bb8e9bdc03c6262fc0b887e" } @@ -11194,8 +11194,8 @@ "databricks": "15f0b4e032d5524678c61cae", "duckdb": "15f0b4e032d5524678c61cae", "generic": "15f0b4e032d5524678c61cae", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "15f0b4e032d5524678c61cae", +"motherduck": "15f0b4e032d5524678c61cae", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "15f0b4e032d5524678c61cae", "postgresql": "15f0b4e032d5524678c61cae", "snowflake": "15f0b4e032d5524678c61cae", @@ -11211,8 +11211,8 @@ "databricks": "cafb5cff5526dd419baad0ed", "duckdb": "cafb5cff5526dd419baad0ed", "generic": "cafb5cff5526dd419baad0ed", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cafb5cff5526dd419baad0ed", +"motherduck": "cafb5cff5526dd419baad0ed", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cafb5cff5526dd419baad0ed", "postgresql": "cafb5cff5526dd419baad0ed", "snowflake": "cafb5cff5526dd419baad0ed", @@ -11228,8 +11228,8 @@ "databricks": "8a3f094f18fb4047f0febf59", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "8a3f094f18fb4047f0febf59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a3f094f18fb4047f0febf59", "postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "850deea990b52b82b36a9848", @@ -11245,10 +11245,10 @@ "databricks": "bf5a83d0c02f8890941c7ec8", "duckdb": "bf5a83d0c02f8890941c7ec8", "generic": "bf5a83d0c02f8890941c7ec8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bf5a83d0c02f8890941c7ec8", -"postgres": "bf5a83d0c02f8890941c7ec8", -"postgresql": "bf5a83d0c02f8890941c7ec8", +"motherduck": "bf5a83d0c02f8890941c7ec8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ef750421d1e193fa61992c79", +"postgresql": "ef750421d1e193fa61992c79", "snowflake": "bf5a83d0c02f8890941c7ec8", "tsql": "bf5a83d0c02f8890941c7ec8" } @@ -11257,15 +11257,15 @@ "type", { "DuckDB": "4c16382f7ad617ac3cbfa833", -"None": "0f63c150afc0bb5bbf7763ba", -"bigquery": "a4d165ed06d0b1b815192ef4", -"databricks": "0f63c150afc0bb5bbf7763ba", +"None": "accf46359aa8c74483b9a197", +"bigquery": "accf46359aa8c74483b9a197", +"databricks": "accf46359aa8c74483b9a197", "duckdb": "4c16382f7ad617ac3cbfa833", -"generic": "0f63c150afc0bb5bbf7763ba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "4c16382f7ad617ac3cbfa833", -"postgresql": "4c16382f7ad617ac3cbfa833", +"generic": "accf46359aa8c74483b9a197", +"motherduck": "4c16382f7ad617ac3cbfa833", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "accf46359aa8c74483b9a197", +"postgresql": "accf46359aa8c74483b9a197", "snowflake": "303d6e0feaedc557ea64e414", "tsql": "accf46359aa8c74483b9a197" } @@ -11274,17 +11274,17 @@ "type", { "DuckDB": "f2ed52f6bf46b4db9343551c", -"None": "f2ed52f6bf46b4db9343551c", -"bigquery": "f10bdd4d40eb495cbcecd31d", -"databricks": "f2ed52f6bf46b4db9343551c", +"None": "3f998a9ab68d8b2514840489", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "3f998a9ab68d8b2514840489", "duckdb": "f2ed52f6bf46b4db9343551c", -"generic": "f2ed52f6bf46b4db9343551c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f2ed52f6bf46b4db9343551c", -"postgres": "f2ed52f6bf46b4db9343551c", -"postgresql": "f2ed52f6bf46b4db9343551c", +"generic": "3f998a9ab68d8b2514840489", +"motherduck": "f2ed52f6bf46b4db9343551c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3f998a9ab68d8b2514840489", +"postgresql": "3f998a9ab68d8b2514840489", "snowflake": "f2ed52f6bf46b4db9343551c", -"tsql": "f2ed52f6bf46b4db9343551c" +"tsql": "3f998a9ab68d8b2514840489" } ], [ @@ -11292,14 +11292,14 @@ { "DuckDB": "c84c0b1d4d7caa6744130456", "None": "c84c0b1d4d7caa6744130456", -"bigquery": "c84c0b1d4d7caa6744130456", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "c84c0b1d4d7caa6744130456", "duckdb": "c84c0b1d4d7caa6744130456", "generic": "c84c0b1d4d7caa6744130456", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c84c0b1d4d7caa6744130456", -"postgres": "c84c0b1d4d7caa6744130456", -"postgresql": "c84c0b1d4d7caa6744130456", +"motherduck": "c84c0b1d4d7caa6744130456", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "725c73e74f311417bb4f95e8", +"postgresql": "725c73e74f311417bb4f95e8", "snowflake": "c84c0b1d4d7caa6744130456", "tsql": "c84c0b1d4d7caa6744130456" } @@ -11309,14 +11309,14 @@ { "DuckDB": "aea8a680fa18770e56bf6220", "None": "aea8a680fa18770e56bf6220", -"bigquery": "aea8a680fa18770e56bf6220", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "aea8a680fa18770e56bf6220", "duckdb": "aea8a680fa18770e56bf6220", "generic": "aea8a680fa18770e56bf6220", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aea8a680fa18770e56bf6220", -"postgres": "aea8a680fa18770e56bf6220", -"postgresql": "aea8a680fa18770e56bf6220", +"motherduck": "aea8a680fa18770e56bf6220", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "836641bf4537dec3e767931d", +"postgresql": "836641bf4537dec3e767931d", "snowflake": "aea8a680fa18770e56bf6220", "tsql": "aea8a680fa18770e56bf6220" } @@ -11330,10 +11330,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -11347,8 +11347,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -11364,8 +11364,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -11381,8 +11381,8 @@ "databricks": "70fb4728c3adcf6dbe4975d5", "duckdb": "70fb4728c3adcf6dbe4975d5", "generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", @@ -11398,8 +11398,8 @@ "databricks": "dcb00b7053ffe69fe3ce99cd", "duckdb": "c7b697bbfa82dd2821ad64e9", "generic": "c7b697bbfa82dd2821ad64e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dcb00b7053ffe69fe3ce99cd", +"motherduck": "c7b697bbfa82dd2821ad64e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c7b697bbfa82dd2821ad64e9", "postgresql": "c7b697bbfa82dd2821ad64e9", "snowflake": "c7b697bbfa82dd2821ad64e9", @@ -11415,8 +11415,8 @@ "databricks": "c0b12bfc586282ce5ea85c45", "duckdb": "c0b12bfc586282ce5ea85c45", "generic": "c0b12bfc586282ce5ea85c45", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c0b12bfc586282ce5ea85c45", +"motherduck": "c0b12bfc586282ce5ea85c45", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c0b12bfc586282ce5ea85c45", "postgresql": "c0b12bfc586282ce5ea85c45", "snowflake": "c0b12bfc586282ce5ea85c45", @@ -11432,8 +11432,8 @@ "databricks": "806bf4b6d83b5dc9948e2054", "duckdb": "806bf4b6d83b5dc9948e2054", "generic": "806bf4b6d83b5dc9948e2054", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "806bf4b6d83b5dc9948e2054", +"motherduck": "806bf4b6d83b5dc9948e2054", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "806bf4b6d83b5dc9948e2054", "postgresql": "806bf4b6d83b5dc9948e2054", "snowflake": "806bf4b6d83b5dc9948e2054", @@ -11449,10 +11449,10 @@ "databricks": "871969531da28b86361e65d2", "duckdb": "871969531da28b86361e65d2", "generic": "871969531da28b86361e65d2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "871969531da28b86361e65d2", -"postgres": "871969531da28b86361e65d2", -"postgresql": "871969531da28b86361e65d2", +"motherduck": "871969531da28b86361e65d2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "990e37f96d40e397fe74978c", +"postgresql": "990e37f96d40e397fe74978c", "snowflake": "871969531da28b86361e65d2", "tsql": "871969531da28b86361e65d2" } @@ -11466,8 +11466,8 @@ "databricks": "4129901e0f313b38452e22af", "duckdb": "4129901e0f313b38452e22af", "generic": "4129901e0f313b38452e22af", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4129901e0f313b38452e22af", +"motherduck": "4129901e0f313b38452e22af", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4129901e0f313b38452e22af", "postgresql": "4129901e0f313b38452e22af", "snowflake": "4129901e0f313b38452e22af", @@ -11483,8 +11483,8 @@ "databricks": "0c290c40483b9651ff557830", "duckdb": "0c290c40483b9651ff557830", "generic": "0c290c40483b9651ff557830", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0c290c40483b9651ff557830", +"motherduck": "0c290c40483b9651ff557830", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0c290c40483b9651ff557830", "postgresql": "0c290c40483b9651ff557830", "snowflake": "0c290c40483b9651ff557830", @@ -11500,8 +11500,8 @@ "databricks": "b1603277660bbe23982ff72a", "duckdb": "b0505024b3f15f191c70a470", "generic": "b1603277660bbe23982ff72a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "b0505024b3f15f191c70a470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fa1a8787c8f69b9f9be89526", "postgresql": "fa1a8787c8f69b9f9be89526", "snowflake": "50404426183626ce7b686f80", @@ -11517,8 +11517,8 @@ "databricks": "b63494be71c6c8936f6824a6", "duckdb": "b63494be71c6c8936f6824a6", "generic": "b63494be71c6c8936f6824a6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b63494be71c6c8936f6824a6", +"motherduck": "b63494be71c6c8936f6824a6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b63494be71c6c8936f6824a6", "postgresql": "b63494be71c6c8936f6824a6", "snowflake": "b63494be71c6c8936f6824a6", @@ -11534,8 +11534,8 @@ "databricks": "016c25f8f0c58947f8da9152", "duckdb": "016c25f8f0c58947f8da9152", "generic": "016c25f8f0c58947f8da9152", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "016c25f8f0c58947f8da9152", +"motherduck": "016c25f8f0c58947f8da9152", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "016c25f8f0c58947f8da9152", "postgresql": "016c25f8f0c58947f8da9152", "snowflake": "016c25f8f0c58947f8da9152", @@ -11551,8 +11551,8 @@ "databricks": "e02779be57c0fcfa69ee7746", "duckdb": "e02779be57c0fcfa69ee7746", "generic": "e02779be57c0fcfa69ee7746", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e02779be57c0fcfa69ee7746", +"motherduck": "e02779be57c0fcfa69ee7746", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e02779be57c0fcfa69ee7746", "postgresql": "e02779be57c0fcfa69ee7746", "snowflake": "e02779be57c0fcfa69ee7746", @@ -11568,8 +11568,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "ace891b6844cb59efad42031", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "ace891b6844cb59efad42031", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -11585,8 +11585,8 @@ "databricks": "b7507b03866507106687a313", "duckdb": "b7507b03866507106687a313", "generic": "b7507b03866507106687a313", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b7507b03866507106687a313", +"motherduck": "b7507b03866507106687a313", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b7507b03866507106687a313", "postgresql": "b7507b03866507106687a313", "snowflake": "b7507b03866507106687a313", @@ -11602,10 +11602,10 @@ "databricks": "fdc9a2fc03f4b67d24982d0a", "duckdb": "fdc9a2fc03f4b67d24982d0a", "generic": "fdc9a2fc03f4b67d24982d0a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fdc9a2fc03f4b67d24982d0a", -"postgres": "fdc9a2fc03f4b67d24982d0a", -"postgresql": "fdc9a2fc03f4b67d24982d0a", +"motherduck": "fdc9a2fc03f4b67d24982d0a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "576eb7ad8e7eca2db50ec386", +"postgresql": "576eb7ad8e7eca2db50ec386", "snowflake": "fdc9a2fc03f4b67d24982d0a", "tsql": "fdc9a2fc03f4b67d24982d0a" } @@ -11614,17 +11614,17 @@ "type", { "DuckDB": "5c8b98f516cedefc237fa721", -"None": "5c8b98f516cedefc237fa721", -"bigquery": "5c8b98f516cedefc237fa721", -"databricks": "5c8b98f516cedefc237fa721", +"None": "81a45386abae5f6be0a027d0", +"bigquery": "09b7f1f9111bc2e0767cc059", +"databricks": "81a45386abae5f6be0a027d0", "duckdb": "5c8b98f516cedefc237fa721", -"generic": "5c8b98f516cedefc237fa721", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c8b98f516cedefc237fa721", +"generic": "81a45386abae5f6be0a027d0", +"motherduck": "5c8b98f516cedefc237fa721", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c8b98f516cedefc237fa721", "postgresql": "5c8b98f516cedefc237fa721", "snowflake": "5c8b98f516cedefc237fa721", -"tsql": "5c8b98f516cedefc237fa721" +"tsql": "81a45386abae5f6be0a027d0" } ], [ @@ -11636,8 +11636,8 @@ "databricks": "8d26a44bbe0d29bdb1509369", "duckdb": "27d7e27586eab431e308413f", "generic": "8d26a44bbe0d29bdb1509369", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8d26a44bbe0d29bdb1509369", +"motherduck": "27d7e27586eab431e308413f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8d26a44bbe0d29bdb1509369", "postgresql": "8d26a44bbe0d29bdb1509369", "snowflake": "8d26a44bbe0d29bdb1509369", @@ -11653,8 +11653,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -11670,8 +11670,8 @@ "databricks": "ab5ad6180652b3b00d1f5b01", "duckdb": "ab5ad6180652b3b00d1f5b01", "generic": "ab5ad6180652b3b00d1f5b01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab5ad6180652b3b00d1f5b01", +"motherduck": "ab5ad6180652b3b00d1f5b01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab5ad6180652b3b00d1f5b01", "postgresql": "ab5ad6180652b3b00d1f5b01", "snowflake": "f43dac38f34a1c0618a3f812", @@ -11687,8 +11687,8 @@ "databricks": "4662a1758442ce44b830269f", "duckdb": "4662a1758442ce44b830269f", "generic": "4662a1758442ce44b830269f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4662a1758442ce44b830269f", +"motherduck": "4662a1758442ce44b830269f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4662a1758442ce44b830269f", "postgresql": "4662a1758442ce44b830269f", "snowflake": "4662a1758442ce44b830269f", @@ -11704,8 +11704,8 @@ "databricks": "34af912a178427fc6e8bdee3", "duckdb": "34af912a178427fc6e8bdee3", "generic": "34af912a178427fc6e8bdee3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "34af912a178427fc6e8bdee3", +"motherduck": "34af912a178427fc6e8bdee3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "34af912a178427fc6e8bdee3", "postgresql": "34af912a178427fc6e8bdee3", "snowflake": "34af912a178427fc6e8bdee3", @@ -11721,8 +11721,8 @@ "databricks": "29e896ae6f2844ec3744aad6", "duckdb": "27d7e27586eab431e308413f", "generic": "29e896ae6f2844ec3744aad6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "29e896ae6f2844ec3744aad6", +"motherduck": "27d7e27586eab431e308413f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "29e896ae6f2844ec3744aad6", "postgresql": "29e896ae6f2844ec3744aad6", "snowflake": "29e896ae6f2844ec3744aad6", @@ -11738,8 +11738,8 @@ "databricks": "e8605878c6fffc044556a858", "duckdb": "e8605878c6fffc044556a858", "generic": "e8605878c6fffc044556a858", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8605878c6fffc044556a858", +"motherduck": "e8605878c6fffc044556a858", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8605878c6fffc044556a858", "postgresql": "e8605878c6fffc044556a858", "snowflake": "e8605878c6fffc044556a858", @@ -11751,14 +11751,14 @@ { "DuckDB": "b561884132dbe7e42b719110", "None": "b561884132dbe7e42b719110", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -11772,10 +11772,10 @@ "databricks": "c6106f30f23f392e17eadef4", "duckdb": "c6106f30f23f392e17eadef4", "generic": "c6106f30f23f392e17eadef4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6106f30f23f392e17eadef4", -"postgres": "c6106f30f23f392e17eadef4", -"postgresql": "c6106f30f23f392e17eadef4", +"motherduck": "c6106f30f23f392e17eadef4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f43dac38f34a1c0618a3f812", +"postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "c6106f30f23f392e17eadef4", "tsql": "c6106f30f23f392e17eadef4" } @@ -11789,8 +11789,8 @@ "databricks": "e7aec8198ab628519324444a", "duckdb": "e7aec8198ab628519324444a", "generic": "e7aec8198ab628519324444a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7aec8198ab628519324444a", +"motherduck": "e7aec8198ab628519324444a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7aec8198ab628519324444a", "postgresql": "e7aec8198ab628519324444a", "snowflake": "e7aec8198ab628519324444a", @@ -11802,14 +11802,14 @@ { "DuckDB": "881106a4a0d2de0ff7c56373", "None": "881106a4a0d2de0ff7c56373", -"bigquery": "881106a4a0d2de0ff7c56373", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "881106a4a0d2de0ff7c56373", "duckdb": "881106a4a0d2de0ff7c56373", "generic": "881106a4a0d2de0ff7c56373", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "881106a4a0d2de0ff7c56373", -"postgres": "881106a4a0d2de0ff7c56373", -"postgresql": "881106a4a0d2de0ff7c56373", +"motherduck": "881106a4a0d2de0ff7c56373", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cf167ec395fc843602e3b1e0", +"postgresql": "cf167ec395fc843602e3b1e0", "snowflake": "881106a4a0d2de0ff7c56373", "tsql": "881106a4a0d2de0ff7c56373" } @@ -11823,10 +11823,10 @@ "databricks": "2c6b371b030464f525066893", "duckdb": "2c6b371b030464f525066893", "generic": "2c6b371b030464f525066893", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c6b371b030464f525066893", -"postgres": "2c6b371b030464f525066893", -"postgresql": "2c6b371b030464f525066893", +"motherduck": "2c6b371b030464f525066893", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3feb479faeec777c2ade8afd", +"postgresql": "3feb479faeec777c2ade8afd", "snowflake": "2c6b371b030464f525066893", "tsql": "2c6b371b030464f525066893" } @@ -11837,11 +11837,11 @@ "DuckDB": "e3d9a113277b9c69432b32d1", "None": "e3d9a113277b9c69432b32d1", "bigquery": "e3d9a113277b9c69432b32d1", -"databricks": "4cda99191af9adfbe91e70f6", +"databricks": "e3d9a113277b9c69432b32d1", "duckdb": "e3d9a113277b9c69432b32d1", "generic": "e3d9a113277b9c69432b32d1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4cda99191af9adfbe91e70f6", +"motherduck": "e3d9a113277b9c69432b32d1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e3d9a113277b9c69432b32d1", "postgresql": "e3d9a113277b9c69432b32d1", "snowflake": "e3d9a113277b9c69432b32d1", @@ -11857,8 +11857,8 @@ "databricks": "ba0da7ef018b990fcc83cba1", "duckdb": "ba0da7ef018b990fcc83cba1", "generic": "ba0da7ef018b990fcc83cba1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ba0da7ef018b990fcc83cba1", +"motherduck": "ba0da7ef018b990fcc83cba1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ba0da7ef018b990fcc83cba1", "postgresql": "ba0da7ef018b990fcc83cba1", "snowflake": "ba0da7ef018b990fcc83cba1", @@ -11874,10 +11874,10 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "396b2ce32efb5365b393c230", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", -"postgres": "e57ac1541fb51b362cd5b470", -"postgresql": "e57ac1541fb51b362cd5b470", +"motherduck": "396b2ce32efb5365b393c230", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "396b2ce32efb5365b393c230", +"postgresql": "396b2ce32efb5365b393c230", "snowflake": "4b356542044f1267fcfc487a", "tsql": "e57ac1541fb51b362cd5b470" } @@ -11886,17 +11886,17 @@ "type", { "DuckDB": "5abf4ec799a2ec32fb10da7e", -"None": "5abf4ec799a2ec32fb10da7e", +"None": "de89632e277dbc8c97115419", "bigquery": "5abf4ec799a2ec32fb10da7e", -"databricks": "5abf4ec799a2ec32fb10da7e", +"databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", -"generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"generic": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", -"tsql": "5abf4ec799a2ec32fb10da7e" +"tsql": "de89632e277dbc8c97115419" } ], [ @@ -11908,8 +11908,8 @@ "databricks": "319a9fbb02ed503eb114164b", "duckdb": "e060b5a5a8e9774b580d58d3", "generic": "e060b5a5a8e9774b580d58d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "319a9fbb02ed503eb114164b", +"motherduck": "e060b5a5a8e9774b580d58d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e060b5a5a8e9774b580d58d3", "postgresql": "e060b5a5a8e9774b580d58d3", "snowflake": "e060b5a5a8e9774b580d58d3", @@ -11925,8 +11925,8 @@ "databricks": "67d251c042daee0bb2bf7351", "duckdb": "67d251c042daee0bb2bf7351", "generic": "67d251c042daee0bb2bf7351", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "67d251c042daee0bb2bf7351", +"motherduck": "67d251c042daee0bb2bf7351", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "67d251c042daee0bb2bf7351", "postgresql": "67d251c042daee0bb2bf7351", "snowflake": "67d251c042daee0bb2bf7351", @@ -11937,17 +11937,17 @@ "type", { "DuckDB": "919b7ec6a7b49d8527b784cd", -"None": "919b7ec6a7b49d8527b784cd", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "e3bc1b8bad22d8104417b00f", +"None": "558da38ec4d9d5d6c72f60cf", +"bigquery": "c50a316d091c821866e4bfba", +"databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "919b7ec6a7b49d8527b784cd", -"generic": "919b7ec6a7b49d8527b784cd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "919b7ec6a7b49d8527b784cd", -"postgres": "a3b6b25265be330ec52a3322", -"postgresql": "a3b6b25265be330ec52a3322", +"generic": "558da38ec4d9d5d6c72f60cf", +"motherduck": "919b7ec6a7b49d8527b784cd", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "558da38ec4d9d5d6c72f60cf", +"postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "919b7ec6a7b49d8527b784cd", -"tsql": "919b7ec6a7b49d8527b784cd" +"tsql": "558da38ec4d9d5d6c72f60cf" } ], [ @@ -11959,8 +11959,8 @@ "databricks": "66ee8490513ef7b131cb4824", "duckdb": "66ee8490513ef7b131cb4824", "generic": "66ee8490513ef7b131cb4824", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "66ee8490513ef7b131cb4824", +"motherduck": "66ee8490513ef7b131cb4824", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66ee8490513ef7b131cb4824", "postgresql": "66ee8490513ef7b131cb4824", "snowflake": "66ee8490513ef7b131cb4824", @@ -11976,8 +11976,8 @@ "databricks": "8f552eefca3955a73672f686", "duckdb": "8f552eefca3955a73672f686", "generic": "8f552eefca3955a73672f686", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8f552eefca3955a73672f686", +"motherduck": "8f552eefca3955a73672f686", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8f552eefca3955a73672f686", "postgresql": "8f552eefca3955a73672f686", "snowflake": "8f552eefca3955a73672f686", @@ -11993,10 +11993,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -12010,8 +12010,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -12027,10 +12027,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -12044,8 +12044,8 @@ "databricks": "853d4786e4edb4f90284765b", "duckdb": "f4c37736a388a1d793d4de1a", "generic": "853d4786e4edb4f90284765b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "f4c37736a388a1d793d4de1a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c37736a388a1d793d4de1a", "postgresql": "f4c37736a388a1d793d4de1a", "snowflake": "a640dde3e800eb6ea27813e3", @@ -12061,8 +12061,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -12073,17 +12073,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -12091,14 +12091,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -12112,8 +12112,8 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19a1a80ffc06169f32694de8", "postgresql": "19a1a80ffc06169f32694de8", "snowflake": "19a1a80ffc06169f32694de8", @@ -12125,14 +12125,14 @@ { "DuckDB": "776e4bf23c8ef10ffaceade2", "None": "776e4bf23c8ef10ffaceade2", -"bigquery": "776e4bf23c8ef10ffaceade2", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "776e4bf23c8ef10ffaceade2", "duckdb": "776e4bf23c8ef10ffaceade2", "generic": "776e4bf23c8ef10ffaceade2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "776e4bf23c8ef10ffaceade2", -"postgres": "776e4bf23c8ef10ffaceade2", -"postgresql": "776e4bf23c8ef10ffaceade2", +"motherduck": "776e4bf23c8ef10ffaceade2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5d06b10843e3e6487fb9906a", +"postgresql": "5d06b10843e3e6487fb9906a", "snowflake": "776e4bf23c8ef10ffaceade2", "tsql": "776e4bf23c8ef10ffaceade2" } @@ -12146,8 +12146,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -12163,10 +12163,10 @@ "databricks": "a3acd7ba35e81a800f967bd3", "duckdb": "42d4a23d9a52e79d0c3458d3", "generic": "a3acd7ba35e81a800f967bd3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a3acd7ba35e81a800f967bd3", -"postgres": "a3acd7ba35e81a800f967bd3", -"postgresql": "a3acd7ba35e81a800f967bd3", +"motherduck": "42d4a23d9a52e79d0c3458d3", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "42d4a23d9a52e79d0c3458d3", +"postgresql": "42d4a23d9a52e79d0c3458d3", "snowflake": "a3acd7ba35e81a800f967bd3", "tsql": "a3acd7ba35e81a800f967bd3" } @@ -12176,14 +12176,14 @@ { "DuckDB": "95050a5e73c2bff179a785b7", "None": "95050a5e73c2bff179a785b7", -"bigquery": "95050a5e73c2bff179a785b7", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "95050a5e73c2bff179a785b7", "duckdb": "95050a5e73c2bff179a785b7", "generic": "95050a5e73c2bff179a785b7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "95050a5e73c2bff179a785b7", -"postgres": "95050a5e73c2bff179a785b7", -"postgresql": "95050a5e73c2bff179a785b7", +"motherduck": "95050a5e73c2bff179a785b7", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e197cb69fc5e5754a25d4038", +"postgresql": "e197cb69fc5e5754a25d4038", "snowflake": "95050a5e73c2bff179a785b7", "tsql": "95050a5e73c2bff179a785b7" } @@ -12197,8 +12197,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -12208,17 +12208,17 @@ [ "type", { -"DuckDB": "89cc3662f602a13aef39eb80", -"None": "f2bbf59a9c02f59ca89e66b6", -"bigquery": "f2bbf59a9c02f59ca89e66b6", -"databricks": "f2bbf59a9c02f59ca89e66b6", -"duckdb": "89cc3662f602a13aef39eb80", -"generic": "f2bbf59a9c02f59ca89e66b6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "89cc3662f602a13aef39eb80", -"postgresql": "89cc3662f602a13aef39eb80", -"snowflake": "6f88ef12d95c26bf8f0bd9e5", +"DuckDB": "01e3dfc024c37a8d62c7f810", +"None": "3a4d8bca5124769db23a43df", +"bigquery": "3a4d8bca5124769db23a43df", +"databricks": "3a4d8bca5124769db23a43df", +"duckdb": "01e3dfc024c37a8d62c7f810", +"generic": "3a4d8bca5124769db23a43df", +"motherduck": "01e3dfc024c37a8d62c7f810", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "01e3dfc024c37a8d62c7f810", +"postgresql": "01e3dfc024c37a8d62c7f810", +"snowflake": "db03b5ccf153c75e43846a6a", "tsql": "ffadc5a4dbb5d511f5cde8d7" } ], @@ -12227,14 +12227,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -12248,8 +12248,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -12265,8 +12265,8 @@ "databricks": "50a940812c6ed2e6dd842e74", "duckdb": "50a940812c6ed2e6dd842e74", "generic": "50a940812c6ed2e6dd842e74", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "50a940812c6ed2e6dd842e74", +"motherduck": "50a940812c6ed2e6dd842e74", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "50a940812c6ed2e6dd842e74", "postgresql": "50a940812c6ed2e6dd842e74", "snowflake": "50a940812c6ed2e6dd842e74", @@ -12282,8 +12282,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -12299,10 +12299,10 @@ "databricks": "279ea7f6f6a3910de04b795b", "duckdb": "279ea7f6f6a3910de04b795b", "generic": "279ea7f6f6a3910de04b795b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "279ea7f6f6a3910de04b795b", -"postgres": "279ea7f6f6a3910de04b795b", -"postgresql": "279ea7f6f6a3910de04b795b", +"motherduck": "279ea7f6f6a3910de04b795b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bc520aebd5872e40030ec7d9", +"postgresql": "bc520aebd5872e40030ec7d9", "snowflake": "279ea7f6f6a3910de04b795b", "tsql": "279ea7f6f6a3910de04b795b" } @@ -12312,12 +12312,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -12328,17 +12328,17 @@ "type", { "DuckDB": "84fca9ac893777959cfd715c", -"None": "84fca9ac893777959cfd715c", -"bigquery": "f28a96f0eb38ad8874f6ec5d", -"databricks": "84fca9ac893777959cfd715c", +"None": "e57ac1541fb51b362cd5b470", +"bigquery": "e57ac1541fb51b362cd5b470", +"databricks": "e57ac1541fb51b362cd5b470", "duckdb": "84fca9ac893777959cfd715c", -"generic": "84fca9ac893777959cfd715c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "84fca9ac893777959cfd715c", -"postgres": "84fca9ac893777959cfd715c", -"postgresql": "84fca9ac893777959cfd715c", +"generic": "e57ac1541fb51b362cd5b470", +"motherduck": "84fca9ac893777959cfd715c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e57ac1541fb51b362cd5b470", +"postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "84fca9ac893777959cfd715c", -"tsql": "6349bc82604b762638e35788" +"tsql": "e57ac1541fb51b362cd5b470" } ], [ @@ -12350,8 +12350,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -12367,8 +12367,8 @@ "databricks": "75a99436882f66b2fdc219e5", "duckdb": "2cc8493833d5ac6046bfbfc2", "generic": "2cc8493833d5ac6046bfbfc2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2cc8493833d5ac6046bfbfc2", +"motherduck": "2cc8493833d5ac6046bfbfc2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2cc8493833d5ac6046bfbfc2", "postgresql": "2cc8493833d5ac6046bfbfc2", "snowflake": "70fb65041d155a676d8443d4", @@ -12384,8 +12384,8 @@ "databricks": "f80980deec760e5b50e05376", "duckdb": "8305d75697b54f509bbc0f53", "generic": "80723d96ed6ed21034faa9bd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f80980deec760e5b50e05376", +"motherduck": "8305d75697b54f509bbc0f53", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8305d75697b54f509bbc0f53", "postgresql": "8305d75697b54f509bbc0f53", "snowflake": "240e7de80b095b23fdb1fd92", @@ -12397,12 +12397,12 @@ { "DuckDB": "d41d40c68507f4d02e348833", "None": "d41d40c68507f4d02e348833", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "d41d40c68507f4d02e348833", "duckdb": "d41d40c68507f4d02e348833", "generic": "d41d40c68507f4d02e348833", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d41d40c68507f4d02e348833", +"motherduck": "d41d40c68507f4d02e348833", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d41d40c68507f4d02e348833", "postgresql": "d41d40c68507f4d02e348833", "snowflake": "d41d40c68507f4d02e348833", @@ -12413,17 +12413,17 @@ "type", { "DuckDB": "2c44dc35fd1ef8701101802e", -"None": "2c44dc35fd1ef8701101802e", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "2c44dc35fd1ef8701101802e", +"None": "8441d00030df06ad6a5534d3", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "8441d00030df06ad6a5534d3", "duckdb": "2c44dc35fd1ef8701101802e", -"generic": "2c44dc35fd1ef8701101802e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c44dc35fd1ef8701101802e", -"postgres": "2c44dc35fd1ef8701101802e", -"postgresql": "2c44dc35fd1ef8701101802e", +"generic": "8441d00030df06ad6a5534d3", +"motherduck": "2c44dc35fd1ef8701101802e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8441d00030df06ad6a5534d3", +"postgresql": "8441d00030df06ad6a5534d3", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "2c44dc35fd1ef8701101802e" +"tsql": "8441d00030df06ad6a5534d3" } ], [ @@ -12431,14 +12431,14 @@ { "DuckDB": "599b3293ed620803ce2401b5", "None": "599b3293ed620803ce2401b5", -"bigquery": "599b3293ed620803ce2401b5", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "599b3293ed620803ce2401b5", "duckdb": "599b3293ed620803ce2401b5", "generic": "599b3293ed620803ce2401b5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "599b3293ed620803ce2401b5", -"postgres": "599b3293ed620803ce2401b5", -"postgresql": "599b3293ed620803ce2401b5", +"motherduck": "599b3293ed620803ce2401b5", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "363a6b7f05b0dac69e1e6547", +"postgresql": "363a6b7f05b0dac69e1e6547", "snowflake": "599b3293ed620803ce2401b5", "tsql": "599b3293ed620803ce2401b5" } @@ -12452,8 +12452,8 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "669084d1ea704ae5b8ee05c2", "postgresql": "669084d1ea704ae5b8ee05c2", "snowflake": "669084d1ea704ae5b8ee05c2", @@ -12469,8 +12469,8 @@ "databricks": "9e4a50a8aab648a9737f7d0c", "duckdb": "9e4a50a8aab648a9737f7d0c", "generic": "9e4a50a8aab648a9737f7d0c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9e4a50a8aab648a9737f7d0c", +"motherduck": "9e4a50a8aab648a9737f7d0c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "36d29258901a4d8c296baa68", "postgresql": "36d29258901a4d8c296baa68", "snowflake": "9e4a50a8aab648a9737f7d0c", @@ -12486,8 +12486,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -12497,16 +12497,16 @@ [ "type", { -"DuckDB": "8731ee5ed03816bf6b4a8464", +"DuckDB": "9c4e957c3856f1159283313a", "None": "8731ee5ed03816bf6b4a8464", "bigquery": "657a69dabb6a9b2c7b834d41", "databricks": "606d813c3f1dfcf4921045c8", -"duckdb": "8731ee5ed03816bf6b4a8464", +"duckdb": "9c4e957c3856f1159283313a", "generic": "8731ee5ed03816bf6b4a8464", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b9f1a78bcda3bc968346cc10", -"postgres": "8731ee5ed03816bf6b4a8464", -"postgresql": "8731ee5ed03816bf6b4a8464", +"motherduck": "9c4e957c3856f1159283313a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9c4e957c3856f1159283313a", +"postgresql": "9c4e957c3856f1159283313a", "snowflake": "cd2eb5ae8b54388fde5d5737", "tsql": "8619573249fac6f54d0bc771" } @@ -12520,8 +12520,8 @@ "databricks": "70fb4728c3adcf6dbe4975d5", "duckdb": "70fb4728c3adcf6dbe4975d5", "generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", @@ -12537,10 +12537,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -12554,8 +12554,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -12565,18 +12565,18 @@ [ "type", { -"DuckDB": "9ebaaf01ad47894d796ac621", -"None": "00caaa5ad0660c8df2165071", +"DuckDB": "0ceb777141e874f9277f0f53", +"None": "18cb3eafdbb91b315055426b", "bigquery": "18cb3eafdbb91b315055426b", -"databricks": "7321b96df327a001959868b6", -"duckdb": "9ebaaf01ad47894d796ac621", -"generic": "00caaa5ad0660c8df2165071", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "00caaa5ad0660c8df2165071", -"postgres": "00caaa5ad0660c8df2165071", -"postgresql": "00caaa5ad0660c8df2165071", +"databricks": "c77c5b64930f0a65094fd575", +"duckdb": "0ceb777141e874f9277f0f53", +"generic": "18cb3eafdbb91b315055426b", +"motherduck": "0ceb777141e874f9277f0f53", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "18cb3eafdbb91b315055426b", +"postgresql": "18cb3eafdbb91b315055426b", "snowflake": "d5bfcc48faca639b47d37309", -"tsql": "00caaa5ad0660c8df2165071" +"tsql": "18cb3eafdbb91b315055426b" } ], [ @@ -12588,8 +12588,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -12600,34 +12600,34 @@ "type", { "DuckDB": "7a79790250ede37cbc407ce6", -"None": "4425df9f96e51bc0984eab91", -"bigquery": "135a912ea74f76a35d9d0632", -"databricks": "4425df9f96e51bc0984eab91", +"None": "398f4aeaabc39054d76e214e", +"bigquery": "398f4aeaabc39054d76e214e", +"databricks": "398f4aeaabc39054d76e214e", "duckdb": "7a79790250ede37cbc407ce6", -"generic": "4425df9f96e51bc0984eab91", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "7a79790250ede37cbc407ce6", -"postgresql": "7a79790250ede37cbc407ce6", -"snowflake": "061ecb71b8d0a25345bb4dc5", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"generic": "398f4aeaabc39054d76e214e", +"motherduck": "7a79790250ede37cbc407ce6", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "398f4aeaabc39054d76e214e", +"postgresql": "398f4aeaabc39054d76e214e", +"snowflake": "398f4aeaabc39054d76e214e", +"tsql": "398f4aeaabc39054d76e214e" } ], [ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -12639,10 +12639,10 @@ "databricks": "d227b99a82e505d599c2ed8a", "duckdb": "35a9c5c1419bff507cf65ae2", "generic": "d227b99a82e505d599c2ed8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d227b99a82e505d599c2ed8a", -"postgres": "d227b99a82e505d599c2ed8a", -"postgresql": "d227b99a82e505d599c2ed8a", +"motherduck": "35a9c5c1419bff507cf65ae2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "35a9c5c1419bff507cf65ae2", +"postgresql": "35a9c5c1419bff507cf65ae2", "snowflake": "d227b99a82e505d599c2ed8a", "tsql": "d227b99a82e505d599c2ed8a" } @@ -12652,12 +12652,12 @@ { "DuckDB": "4ed568ea1adb2c8a7141be20", "None": "b981394a3f4d961a44e73868", -"bigquery": "b981394a3f4d961a44e73868", +"bigquery": "135a912ea74f76a35d9d0632", "databricks": "b981394a3f4d961a44e73868", "duckdb": "4ed568ea1adb2c8a7141be20", "generic": "b981394a3f4d961a44e73868", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "4ed568ea1adb2c8a7141be20", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4ed568ea1adb2c8a7141be20", "postgresql": "4ed568ea1adb2c8a7141be20", "snowflake": "b63ef04215d2dd65ccf32905", @@ -12673,8 +12673,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -12690,8 +12690,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -12702,32 +12702,32 @@ "type", { "DuckDB": "f46cf4b4e131aa122b5b33d6", -"None": "f46cf4b4e131aa122b5b33d6", -"bigquery": "f46cf4b4e131aa122b5b33d6", -"databricks": "f46cf4b4e131aa122b5b33d6", +"None": "f774d5e8feadbf11edaa26b1", +"bigquery": "9bafb08d41fd1c208b5deca4", +"databricks": "f774d5e8feadbf11edaa26b1", "duckdb": "f46cf4b4e131aa122b5b33d6", -"generic": "f46cf4b4e131aa122b5b33d6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f46cf4b4e131aa122b5b33d6", +"generic": "f774d5e8feadbf11edaa26b1", +"motherduck": "f46cf4b4e131aa122b5b33d6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f46cf4b4e131aa122b5b33d6", "postgresql": "f46cf4b4e131aa122b5b33d6", "snowflake": "f46cf4b4e131aa122b5b33d6", -"tsql": "f46cf4b4e131aa122b5b33d6" +"tsql": "f774d5e8feadbf11edaa26b1" } ], [ "type", { -"DuckDB": "4172129e8d84a0da597bc612", +"DuckDB": "0da0fad3107f7a26744c9411", "None": "4172129e8d84a0da597bc612", "bigquery": "d138b3b3355094a33a14355a", "databricks": "4172129e8d84a0da597bc612", -"duckdb": "4172129e8d84a0da597bc612", +"duckdb": "0da0fad3107f7a26744c9411", "generic": "4172129e8d84a0da597bc612", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "4172129e8d84a0da597bc612", -"postgresql": "4172129e8d84a0da597bc612", +"motherduck": "0da0fad3107f7a26744c9411", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "0da0fad3107f7a26744c9411", +"postgresql": "0da0fad3107f7a26744c9411", "snowflake": "e21aafb74f6e5c5e181abd5e", "tsql": "2e261f2f5a01e0e0f4a34a7a" } @@ -12741,8 +12741,8 @@ "databricks": "5c528c7b4329aeb692ec6e94", "duckdb": "5c528c7b4329aeb692ec6e94", "generic": "5c528c7b4329aeb692ec6e94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c528c7b4329aeb692ec6e94", +"motherduck": "5c528c7b4329aeb692ec6e94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c528c7b4329aeb692ec6e94", "postgresql": "5c528c7b4329aeb692ec6e94", "snowflake": "5c528c7b4329aeb692ec6e94", @@ -12758,8 +12758,8 @@ "databricks": "b66ef401bfc62ea5f67697f0", "duckdb": "b66ef401bfc62ea5f67697f0", "generic": "b66ef401bfc62ea5f67697f0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b66ef401bfc62ea5f67697f0", +"motherduck": "b66ef401bfc62ea5f67697f0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b66ef401bfc62ea5f67697f0", "postgresql": "b66ef401bfc62ea5f67697f0", "snowflake": "b66ef401bfc62ea5f67697f0", @@ -12769,18 +12769,18 @@ [ "type", { -"DuckDB": "67b955e24db58c73ccf37441", -"None": "7390a6531648a24d91197039", +"DuckDB": "8a72102ccfafa4befa09244b", +"None": "41beda08d4df864cb9dd8402", "bigquery": "41beda08d4df864cb9dd8402", "databricks": "41beda08d4df864cb9dd8402", -"duckdb": "67b955e24db58c73ccf37441", -"generic": "7390a6531648a24d91197039", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "41beda08d4df864cb9dd8402", -"postgres": "4d6c7e32aac20e9e4fb2369e", -"postgresql": "4d6c7e32aac20e9e4fb2369e", +"duckdb": "8a72102ccfafa4befa09244b", +"generic": "41beda08d4df864cb9dd8402", +"motherduck": "8a72102ccfafa4befa09244b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "41beda08d4df864cb9dd8402", +"postgresql": "41beda08d4df864cb9dd8402", "snowflake": "bae18d740565e7e0e416c8f3", -"tsql": "762b596d819e7050cff38fb8" +"tsql": "41beda08d4df864cb9dd8402" } ], [ @@ -12788,14 +12788,14 @@ { "DuckDB": "c4346d0257336219f1d26890", "None": "c4346d0257336219f1d26890", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "c4346d0257336219f1d26890", "duckdb": "c4346d0257336219f1d26890", "generic": "c4346d0257336219f1d26890", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c4346d0257336219f1d26890", -"postgres": "c4346d0257336219f1d26890", -"postgresql": "c4346d0257336219f1d26890", +"motherduck": "c4346d0257336219f1d26890", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "19bdd92ea0cc5fb028657510", +"postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "4e24a80b20b9dee0cbbfac17", "tsql": "c4346d0257336219f1d26890" } @@ -12809,8 +12809,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -12826,8 +12826,8 @@ "databricks": "b137c998bdaa2555d912e8fa", "duckdb": "27f973a6b2da27a79f5c0074", "generic": "b137c998bdaa2555d912e8fa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b137c998bdaa2555d912e8fa", +"motherduck": "27f973a6b2da27a79f5c0074", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b137c998bdaa2555d912e8fa", "postgresql": "b137c998bdaa2555d912e8fa", "snowflake": "b137c998bdaa2555d912e8fa", @@ -12843,8 +12843,8 @@ "databricks": "ab5ad6180652b3b00d1f5b01", "duckdb": "ab5ad6180652b3b00d1f5b01", "generic": "ab5ad6180652b3b00d1f5b01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab5ad6180652b3b00d1f5b01", +"motherduck": "ab5ad6180652b3b00d1f5b01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab5ad6180652b3b00d1f5b01", "postgresql": "ab5ad6180652b3b00d1f5b01", "snowflake": "f43dac38f34a1c0618a3f812", @@ -12860,10 +12860,10 @@ "databricks": "336fd1b7a1f7d8105c29280c", "duckdb": "0baaca265927140414f32b37", "generic": "336fd1b7a1f7d8105c29280c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "336fd1b7a1f7d8105c29280c", -"postgres": "336fd1b7a1f7d8105c29280c", -"postgresql": "336fd1b7a1f7d8105c29280c", +"motherduck": "0baaca265927140414f32b37", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9ea4932505663b43f527adad", +"postgresql": "9ea4932505663b43f527adad", "snowflake": "336fd1b7a1f7d8105c29280c", "tsql": "336fd1b7a1f7d8105c29280c" } @@ -12877,10 +12877,10 @@ "databricks": "0960c90223e1eca15361360f", "duckdb": "0960c90223e1eca15361360f", "generic": "0960c90223e1eca15361360f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0960c90223e1eca15361360f", -"postgres": "0960c90223e1eca15361360f", -"postgresql": "0960c90223e1eca15361360f", +"motherduck": "0960c90223e1eca15361360f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b72367ce0764c938c620a44c", +"postgresql": "b72367ce0764c938c620a44c", "snowflake": "0960c90223e1eca15361360f", "tsql": "0960c90223e1eca15361360f" } @@ -12894,8 +12894,8 @@ "databricks": "b46dfa976d5ba04f95bea32b", "duckdb": "b46dfa976d5ba04f95bea32b", "generic": "b46dfa976d5ba04f95bea32b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b46dfa976d5ba04f95bea32b", +"motherduck": "b46dfa976d5ba04f95bea32b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "34dcdfe3e3afe6140e1353e0", "postgresql": "34dcdfe3e3afe6140e1353e0", "snowflake": "b46dfa976d5ba04f95bea32b", @@ -12911,8 +12911,8 @@ "databricks": "b66ef401bfc62ea5f67697f0", "duckdb": "b66ef401bfc62ea5f67697f0", "generic": "b66ef401bfc62ea5f67697f0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b66ef401bfc62ea5f67697f0", +"motherduck": "b66ef401bfc62ea5f67697f0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b66ef401bfc62ea5f67697f0", "postgresql": "b66ef401bfc62ea5f67697f0", "snowflake": "b66ef401bfc62ea5f67697f0", @@ -12924,14 +12924,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -12945,8 +12945,8 @@ "databricks": "d90a78129f7050a540984f6f", "duckdb": "66f7ca02161349d637fa7d4e", "generic": "66f7ca02161349d637fa7d4e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d90a78129f7050a540984f6f", +"motherduck": "66f7ca02161349d637fa7d4e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66f7ca02161349d637fa7d4e", "postgresql": "66f7ca02161349d637fa7d4e", "snowflake": "66f7ca02161349d637fa7d4e", @@ -12956,18 +12956,18 @@ [ "type", { -"DuckDB": "6b7315cdb8da9cc8682fe582", -"None": "5db36cde08c21101926533c6", -"bigquery": "be1298bd832c06e2fbed16cc", -"databricks": "68c9a65b028d0f4e1e431d2b", -"duckdb": "6b7315cdb8da9cc8682fe582", -"generic": "5db36cde08c21101926533c6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3a28322a1d9fe5f73ddf21a6", -"postgres": "627a2125b73642795b93f3ec", -"postgresql": "627a2125b73642795b93f3ec", +"DuckDB": "20949d5f5c24adb922f1df86", +"None": "8c977bea8b7e2befaacba9a7", +"bigquery": "8c977bea8b7e2befaacba9a7", +"databricks": "8c977bea8b7e2befaacba9a7", +"duckdb": "20949d5f5c24adb922f1df86", +"generic": "8c977bea8b7e2befaacba9a7", +"motherduck": "20949d5f5c24adb922f1df86", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8c977bea8b7e2befaacba9a7", +"postgresql": "8c977bea8b7e2befaacba9a7", "snowflake": "c990ce1e11ec35da455f1547", -"tsql": "5db36cde08c21101926533c6" +"tsql": "8c977bea8b7e2befaacba9a7" } ], [ @@ -12979,10 +12979,10 @@ "databricks": "26e466cea9d3c66c226e9bbb", "duckdb": "26e466cea9d3c66c226e9bbb", "generic": "26e466cea9d3c66c226e9bbb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "26e466cea9d3c66c226e9bbb", -"postgres": "26e466cea9d3c66c226e9bbb", -"postgresql": "26e466cea9d3c66c226e9bbb", +"motherduck": "26e466cea9d3c66c226e9bbb", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a1fa261c560b019889bf4835", +"postgresql": "a1fa261c560b019889bf4835", "snowflake": "26e466cea9d3c66c226e9bbb", "tsql": "26e466cea9d3c66c226e9bbb" } @@ -12991,34 +12991,34 @@ "type", { "DuckDB": "31efc7b307bffec917380e8e", -"None": "31efc7b307bffec917380e8e", -"bigquery": "31efc7b307bffec917380e8e", -"databricks": "31efc7b307bffec917380e8e", +"None": "ed874ecba919fbd97b7f6780", +"bigquery": "ed874ecba919fbd97b7f6780", +"databricks": "ed874ecba919fbd97b7f6780", "duckdb": "31efc7b307bffec917380e8e", -"generic": "31efc7b307bffec917380e8e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "31efc7b307bffec917380e8e", +"generic": "ed874ecba919fbd97b7f6780", +"motherduck": "31efc7b307bffec917380e8e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "31efc7b307bffec917380e8e", "postgresql": "31efc7b307bffec917380e8e", "snowflake": "42d4a23d9a52e79d0c3458d3", -"tsql": "31efc7b307bffec917380e8e" +"tsql": "ed874ecba919fbd97b7f6780" } ], [ "type", { "DuckDB": "2f9ac8bd6831b78665301f10", -"None": "2f9ac8bd6831b78665301f10", -"bigquery": "2f9ac8bd6831b78665301f10", -"databricks": "2f9ac8bd6831b78665301f10", +"None": "c557f7ce51bc180332354184", +"bigquery": "09b7f1f9111bc2e0767cc059", +"databricks": "c557f7ce51bc180332354184", "duckdb": "2f9ac8bd6831b78665301f10", -"generic": "2f9ac8bd6831b78665301f10", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2f9ac8bd6831b78665301f10", +"generic": "c557f7ce51bc180332354184", +"motherduck": "2f9ac8bd6831b78665301f10", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2f9ac8bd6831b78665301f10", "postgresql": "2f9ac8bd6831b78665301f10", "snowflake": "2f9ac8bd6831b78665301f10", -"tsql": "2f9ac8bd6831b78665301f10" +"tsql": "c557f7ce51bc180332354184" } ], [ @@ -13030,8 +13030,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -13047,8 +13047,8 @@ "databricks": "7102b9514dfef58f8673a6bf", "duckdb": "7102b9514dfef58f8673a6bf", "generic": "7102b9514dfef58f8673a6bf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7102b9514dfef58f8673a6bf", +"motherduck": "7102b9514dfef58f8673a6bf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7102b9514dfef58f8673a6bf", "postgresql": "7102b9514dfef58f8673a6bf", "snowflake": "7102b9514dfef58f8673a6bf", @@ -13064,8 +13064,8 @@ "databricks": "6d7ab232e65fe8d85c7169ae", "duckdb": "6d7ab232e65fe8d85c7169ae", "generic": "6d7ab232e65fe8d85c7169ae", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6d7ab232e65fe8d85c7169ae", +"motherduck": "6d7ab232e65fe8d85c7169ae", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a6659d96bbfa5da75e69e22d", "postgresql": "a6659d96bbfa5da75e69e22d", "snowflake": "6d7ab232e65fe8d85c7169ae", @@ -13081,8 +13081,8 @@ "databricks": "e7683084f9cefaf3748cf526", "duckdb": "e7683084f9cefaf3748cf526", "generic": "e7683084f9cefaf3748cf526", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7683084f9cefaf3748cf526", +"motherduck": "e7683084f9cefaf3748cf526", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7683084f9cefaf3748cf526", "postgresql": "e7683084f9cefaf3748cf526", "snowflake": "e7683084f9cefaf3748cf526", @@ -13098,8 +13098,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -13115,8 +13115,8 @@ "databricks": "50a940812c6ed2e6dd842e74", "duckdb": "50a940812c6ed2e6dd842e74", "generic": "50a940812c6ed2e6dd842e74", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "50a940812c6ed2e6dd842e74", +"motherduck": "50a940812c6ed2e6dd842e74", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "50a940812c6ed2e6dd842e74", "postgresql": "50a940812c6ed2e6dd842e74", "snowflake": "50a940812c6ed2e6dd842e74", @@ -13132,8 +13132,8 @@ "databricks": "e1c74d96ac578f9323bf1bbd", "duckdb": "e1c74d96ac578f9323bf1bbd", "generic": "e1c74d96ac578f9323bf1bbd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e1c74d96ac578f9323bf1bbd", +"motherduck": "e1c74d96ac578f9323bf1bbd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e1c74d96ac578f9323bf1bbd", "postgresql": "e1c74d96ac578f9323bf1bbd", "snowflake": "e1c74d96ac578f9323bf1bbd", @@ -13149,8 +13149,8 @@ "databricks": "d6e37cd37419bfb26e09b080", "duckdb": "d6e37cd37419bfb26e09b080", "generic": "d6e37cd37419bfb26e09b080", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d6e37cd37419bfb26e09b080", +"motherduck": "d6e37cd37419bfb26e09b080", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d6e37cd37419bfb26e09b080", "postgresql": "d6e37cd37419bfb26e09b080", "snowflake": "d6e37cd37419bfb26e09b080", @@ -13166,10 +13166,10 @@ "databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -13178,16 +13178,16 @@ "type", { "DuckDB": "7a79790250ede37cbc407ce6", -"None": "4425df9f96e51bc0984eab91", -"bigquery": "135a912ea74f76a35d9d0632", -"databricks": "4425df9f96e51bc0984eab91", +"None": "45809055304b61252670382f", +"bigquery": "45809055304b61252670382f", +"databricks": "45809055304b61252670382f", "duckdb": "7a79790250ede37cbc407ce6", -"generic": "4425df9f96e51bc0984eab91", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "7a79790250ede37cbc407ce6", -"postgresql": "7a79790250ede37cbc407ce6", -"snowflake": "061ecb71b8d0a25345bb4dc5", +"generic": "45809055304b61252670382f", +"motherduck": "7a79790250ede37cbc407ce6", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "45809055304b61252670382f", +"postgresql": "45809055304b61252670382f", +"snowflake": "45809055304b61252670382f", "tsql": "45809055304b61252670382f" } ], @@ -13200,8 +13200,8 @@ "databricks": "dcb00b7053ffe69fe3ce99cd", "duckdb": "c7b697bbfa82dd2821ad64e9", "generic": "c7b697bbfa82dd2821ad64e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dcb00b7053ffe69fe3ce99cd", +"motherduck": "c7b697bbfa82dd2821ad64e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c7b697bbfa82dd2821ad64e9", "postgresql": "c7b697bbfa82dd2821ad64e9", "snowflake": "c7b697bbfa82dd2821ad64e9", @@ -13217,8 +13217,8 @@ "databricks": "66acfa34471e60551c71ca8a", "duckdb": "66acfa34471e60551c71ca8a", "generic": "66acfa34471e60551c71ca8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "66acfa34471e60551c71ca8a", +"motherduck": "66acfa34471e60551c71ca8a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66acfa34471e60551c71ca8a", "postgresql": "66acfa34471e60551c71ca8a", "snowflake": "6306b9e88f7b13dbb6a32c00", @@ -13234,8 +13234,8 @@ "databricks": "bca4e355ae48af6503cb15b3", "duckdb": "793afe64018cd40f2abf8cd7", "generic": "793afe64018cd40f2abf8cd7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "793afe64018cd40f2abf8cd7", +"motherduck": "793afe64018cd40f2abf8cd7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "793afe64018cd40f2abf8cd7", "postgresql": "793afe64018cd40f2abf8cd7", "snowflake": "c0351067a884fbee21c680b3", @@ -13251,8 +13251,8 @@ "databricks": "0c40d2d16c130d68a7c6c41d", "duckdb": "0c40d2d16c130d68a7c6c41d", "generic": "0c40d2d16c130d68a7c6c41d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0c40d2d16c130d68a7c6c41d", +"motherduck": "0c40d2d16c130d68a7c6c41d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0c40d2d16c130d68a7c6c41d", "postgresql": "0c40d2d16c130d68a7c6c41d", "snowflake": "0c40d2d16c130d68a7c6c41d", @@ -13268,10 +13268,10 @@ "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", -"postgres": "18db5afe2c1e80d1ee652b54", -"postgresql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "47d64a5994d42a2b577166ea", +"postgresql": "47d64a5994d42a2b577166ea", "snowflake": "18db5afe2c1e80d1ee652b54", "tsql": "18db5afe2c1e80d1ee652b54" } @@ -13285,8 +13285,8 @@ "databricks": "f73576af0cd50cbe498a0092", "duckdb": "f73576af0cd50cbe498a0092", "generic": "f73576af0cd50cbe498a0092", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f73576af0cd50cbe498a0092", +"motherduck": "f73576af0cd50cbe498a0092", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f73576af0cd50cbe498a0092", "postgresql": "f73576af0cd50cbe498a0092", "snowflake": "f73576af0cd50cbe498a0092", @@ -13302,8 +13302,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -13319,8 +13319,8 @@ "databricks": "83fdbd1f0a6ee4e72faa4d94", "duckdb": "83fdbd1f0a6ee4e72faa4d94", "generic": "83fdbd1f0a6ee4e72faa4d94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "83fdbd1f0a6ee4e72faa4d94", +"motherduck": "83fdbd1f0a6ee4e72faa4d94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "83fdbd1f0a6ee4e72faa4d94", "postgresql": "83fdbd1f0a6ee4e72faa4d94", "snowflake": "83fdbd1f0a6ee4e72faa4d94", @@ -13336,8 +13336,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "355e032cc5c0f24695836b3d", "generic": "355e032cc5c0f24695836b3d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "355e032cc5c0f24695836b3d", +"motherduck": "355e032cc5c0f24695836b3d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b0b09c4a6f7570ece56f59ce", "postgresql": "b0b09c4a6f7570ece56f59ce", "snowflake": "355e032cc5c0f24695836b3d", @@ -13353,8 +13353,8 @@ "databricks": "a40042061a75dff39acf27ea", "duckdb": "a40042061a75dff39acf27ea", "generic": "a40042061a75dff39acf27ea", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a40042061a75dff39acf27ea", +"motherduck": "a40042061a75dff39acf27ea", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a40042061a75dff39acf27ea", "postgresql": "a40042061a75dff39acf27ea", "snowflake": "a40042061a75dff39acf27ea", @@ -13366,14 +13366,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -13383,12 +13383,12 @@ { "DuckDB": "d0c95c7a10940244afe0eb6c", "None": "d0c95c7a10940244afe0eb6c", -"bigquery": "23ee196b7bd6039855a1460f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "d0c95c7a10940244afe0eb6c", "duckdb": "d0c95c7a10940244afe0eb6c", "generic": "d0c95c7a10940244afe0eb6c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d0c95c7a10940244afe0eb6c", +"motherduck": "d0c95c7a10940244afe0eb6c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d0c95c7a10940244afe0eb6c", "postgresql": "d0c95c7a10940244afe0eb6c", "snowflake": "d0c95c7a10940244afe0eb6c", @@ -13400,12 +13400,12 @@ { "DuckDB": "5c28019dce48a74708c90a76", "None": "5c28019dce48a74708c90a76", -"bigquery": "f28a96f0eb38ad8874f6ec5d", +"bigquery": "5c28019dce48a74708c90a76", "databricks": "5c28019dce48a74708c90a76", "duckdb": "5c28019dce48a74708c90a76", "generic": "5c28019dce48a74708c90a76", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "5c28019dce48a74708c90a76", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c28019dce48a74708c90a76", "postgresql": "5c28019dce48a74708c90a76", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -13421,8 +13421,8 @@ "databricks": "2e4ac90e0dc84f220169a428", "duckdb": "2e4ac90e0dc84f220169a428", "generic": "2e4ac90e0dc84f220169a428", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2e4ac90e0dc84f220169a428", +"motherduck": "2e4ac90e0dc84f220169a428", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2e4ac90e0dc84f220169a428", "postgresql": "2e4ac90e0dc84f220169a428", "snowflake": "2e4ac90e0dc84f220169a428", @@ -13438,8 +13438,8 @@ "databricks": "f94b458b28b4e92e314740bf", "duckdb": "f94b458b28b4e92e314740bf", "generic": "f94b458b28b4e92e314740bf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f94b458b28b4e92e314740bf", +"motherduck": "f94b458b28b4e92e314740bf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f94b458b28b4e92e314740bf", "postgresql": "f94b458b28b4e92e314740bf", "snowflake": "f94b458b28b4e92e314740bf", @@ -13455,10 +13455,10 @@ "databricks": "2705f19942a4194a72576911", "duckdb": "2705f19942a4194a72576911", "generic": "2705f19942a4194a72576911", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2705f19942a4194a72576911", -"postgres": "2705f19942a4194a72576911", -"postgresql": "2705f19942a4194a72576911", +"motherduck": "2705f19942a4194a72576911", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5226d7e2a62c7650a87d6e84", +"postgresql": "5226d7e2a62c7650a87d6e84", "snowflake": "2705f19942a4194a72576911", "tsql": "2705f19942a4194a72576911" } @@ -13472,8 +13472,8 @@ "databricks": "42a76d5ff05d0f91911ff368", "duckdb": "42a76d5ff05d0f91911ff368", "generic": "42a76d5ff05d0f91911ff368", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "42a76d5ff05d0f91911ff368", +"motherduck": "42a76d5ff05d0f91911ff368", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "42a76d5ff05d0f91911ff368", "postgresql": "42a76d5ff05d0f91911ff368", "snowflake": "42a76d5ff05d0f91911ff368", @@ -13485,14 +13485,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -13506,8 +13506,8 @@ "databricks": "f94b458b28b4e92e314740bf", "duckdb": "f94b458b28b4e92e314740bf", "generic": "f94b458b28b4e92e314740bf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f94b458b28b4e92e314740bf", +"motherduck": "f94b458b28b4e92e314740bf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f94b458b28b4e92e314740bf", "postgresql": "f94b458b28b4e92e314740bf", "snowflake": "f94b458b28b4e92e314740bf", @@ -13523,10 +13523,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -13540,8 +13540,8 @@ "databricks": "7c11e0be7630905d93bd9ce1", "duckdb": "7c11e0be7630905d93bd9ce1", "generic": "7c11e0be7630905d93bd9ce1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7c11e0be7630905d93bd9ce1", +"motherduck": "7c11e0be7630905d93bd9ce1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7c11e0be7630905d93bd9ce1", "postgresql": "7c11e0be7630905d93bd9ce1", "snowflake": "7c11e0be7630905d93bd9ce1", @@ -13557,8 +13557,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -13570,14 +13570,14 @@ { "DuckDB": "181c436b3522e70edf3adf58", "None": "181c436b3522e70edf3adf58", -"bigquery": "181c436b3522e70edf3adf58", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "181c436b3522e70edf3adf58", "duckdb": "181c436b3522e70edf3adf58", "generic": "181c436b3522e70edf3adf58", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "181c436b3522e70edf3adf58", -"postgres": "181c436b3522e70edf3adf58", -"postgresql": "181c436b3522e70edf3adf58", +"motherduck": "181c436b3522e70edf3adf58", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7560dc4d0678d613b6142654", +"postgresql": "7560dc4d0678d613b6142654", "snowflake": "181c436b3522e70edf3adf58", "tsql": "181c436b3522e70edf3adf58" } @@ -13585,18 +13585,18 @@ [ "type", { -"DuckDB": "24c3d9dcce7ee2c5b2bac1df", -"None": "547c73a68a3263d4ca786f4d", -"bigquery": "547c73a68a3263d4ca786f4d", -"databricks": "3ad35a92f1fdd0560316b488", -"duckdb": "24c3d9dcce7ee2c5b2bac1df", -"generic": "547c73a68a3263d4ca786f4d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "24c3d9dcce7ee2c5b2bac1df", -"postgresql": "24c3d9dcce7ee2c5b2bac1df", -"snowflake": "0591bc1bf9f4f62b80103d53", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"DuckDB": "2bd744f24899a7b211c526d9", +"None": "2bd744f24899a7b211c526d9", +"bigquery": "2bd744f24899a7b211c526d9", +"databricks": "2bd744f24899a7b211c526d9", +"duckdb": "2bd744f24899a7b211c526d9", +"generic": "2bd744f24899a7b211c526d9", +"motherduck": "2bd744f24899a7b211c526d9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2bd744f24899a7b211c526d9", +"postgresql": "2bd744f24899a7b211c526d9", +"snowflake": "2bd744f24899a7b211c526d9", +"tsql": "2bd744f24899a7b211c526d9" } ], [ @@ -13608,8 +13608,8 @@ "databricks": "b60a7259ca26ea325ba090a1", "duckdb": "b60a7259ca26ea325ba090a1", "generic": "b60a7259ca26ea325ba090a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b60a7259ca26ea325ba090a1", +"motherduck": "b60a7259ca26ea325ba090a1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b60a7259ca26ea325ba090a1", "postgresql": "b60a7259ca26ea325ba090a1", "snowflake": "b60a7259ca26ea325ba090a1", @@ -13625,8 +13625,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -13636,14 +13636,14 @@ [ "type", { -"DuckDB": "285056becb5d467811be7cd6", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "285056becb5d467811be7cd6", "bigquery": "0712c33a016f7dd6a37ee011", "databricks": "285056becb5d467811be7cd6", -"duckdb": "285056becb5d467811be7cd6", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "285056becb5d467811be7cd6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fc10fc558d906a80a6404b7f", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "285056becb5d467811be7cd6", "postgresql": "285056becb5d467811be7cd6", "snowflake": "0712c33a016f7dd6a37ee011", @@ -13655,14 +13655,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -13672,14 +13672,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -13688,17 +13688,17 @@ "type", { "DuckDB": "a3b6b25265be330ec52a3322", -"None": "a3b6b25265be330ec52a3322", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "e3bc1b8bad22d8104417b00f", +"None": "62368fc15cefd5807807b209", +"bigquery": "c50a316d091c821866e4bfba", +"databricks": "62368fc15cefd5807807b209", "duckdb": "a3b6b25265be330ec52a3322", -"generic": "a3b6b25265be330ec52a3322", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "919b7ec6a7b49d8527b784cd", -"postgres": "a3b6b25265be330ec52a3322", -"postgresql": "a3b6b25265be330ec52a3322", +"generic": "62368fc15cefd5807807b209", +"motherduck": "a3b6b25265be330ec52a3322", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62368fc15cefd5807807b209", +"postgresql": "62368fc15cefd5807807b209", "snowflake": "919b7ec6a7b49d8527b784cd", -"tsql": "a3b6b25265be330ec52a3322" +"tsql": "62368fc15cefd5807807b209" } ], [ @@ -13706,14 +13706,14 @@ { "DuckDB": "4d0f0db1a550ed1e0da1a043", "None": "4d0f0db1a550ed1e0da1a043", -"bigquery": "4d0f0db1a550ed1e0da1a043", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "4d0f0db1a550ed1e0da1a043", "duckdb": "4d0f0db1a550ed1e0da1a043", "generic": "4d0f0db1a550ed1e0da1a043", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4d0f0db1a550ed1e0da1a043", -"postgres": "4d0f0db1a550ed1e0da1a043", -"postgresql": "4d0f0db1a550ed1e0da1a043", +"motherduck": "4d0f0db1a550ed1e0da1a043", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e47320d1030c166a05193bc4", +"postgresql": "e47320d1030c166a05193bc4", "snowflake": "4d0f0db1a550ed1e0da1a043", "tsql": "4d0f0db1a550ed1e0da1a043" } @@ -13722,17 +13722,17 @@ "type", { "DuckDB": "6f4372a332076048e56d5076", -"None": "6f4372a332076048e56d5076", -"bigquery": "74a30f6aa9def0b58afc616c", -"databricks": "6f4372a332076048e56d5076", +"None": "fa78cfa46a78783d069e2fc5", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "fa78cfa46a78783d069e2fc5", "duckdb": "6f4372a332076048e56d5076", -"generic": "6f4372a332076048e56d5076", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6f4372a332076048e56d5076", +"generic": "fa78cfa46a78783d069e2fc5", +"motherduck": "6f4372a332076048e56d5076", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6f4372a332076048e56d5076", "postgresql": "6f4372a332076048e56d5076", "snowflake": "daeeb0f00b3ee4211b8da00f", -"tsql": "6f4372a332076048e56d5076" +"tsql": "fa78cfa46a78783d069e2fc5" } ], [ @@ -13740,12 +13740,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -13761,8 +13761,8 @@ "databricks": "e8b6e0bd2ded7b9eec6209e4", "duckdb": "e8b6e0bd2ded7b9eec6209e4", "generic": "e8b6e0bd2ded7b9eec6209e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8b6e0bd2ded7b9eec6209e4", +"motherduck": "e8b6e0bd2ded7b9eec6209e4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8b6e0bd2ded7b9eec6209e4", "postgresql": "e8b6e0bd2ded7b9eec6209e4", "snowflake": "e8b6e0bd2ded7b9eec6209e4", @@ -13773,17 +13773,17 @@ "type", { "DuckDB": "087a9417cb511ff7bcfd6ccb", -"None": "383f8922f4b6f167d384037d", -"bigquery": "383f8922f4b6f167d384037d", -"databricks": "383f8922f4b6f167d384037d", +"None": "2e8f6dfec4597fb9373329e7", +"bigquery": "2e8f6dfec4597fb9373329e7", +"databricks": "2e8f6dfec4597fb9373329e7", "duckdb": "087a9417cb511ff7bcfd6ccb", -"generic": "383f8922f4b6f167d384037d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "383f8922f4b6f167d384037d", +"generic": "2e8f6dfec4597fb9373329e7", +"motherduck": "087a9417cb511ff7bcfd6ccb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "383f8922f4b6f167d384037d", "postgresql": "383f8922f4b6f167d384037d", "snowflake": "383f8922f4b6f167d384037d", -"tsql": "383f8922f4b6f167d384037d" +"tsql": "2e8f6dfec4597fb9373329e7" } ], [ @@ -13791,12 +13791,12 @@ { "DuckDB": "e08a957777bd278f921ca0ee", "None": "e08a957777bd278f921ca0ee", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "e08a957777bd278f921ca0ee", "duckdb": "e08a957777bd278f921ca0ee", "generic": "e08a957777bd278f921ca0ee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e08a957777bd278f921ca0ee", +"motherduck": "e08a957777bd278f921ca0ee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e08a957777bd278f921ca0ee", "postgresql": "e08a957777bd278f921ca0ee", "snowflake": "e08a957777bd278f921ca0ee", @@ -13812,8 +13812,8 @@ "databricks": "d79c3ac70093cb4cb531e71c", "duckdb": "d79c3ac70093cb4cb531e71c", "generic": "d79c3ac70093cb4cb531e71c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d79c3ac70093cb4cb531e71c", +"motherduck": "d79c3ac70093cb4cb531e71c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d79c3ac70093cb4cb531e71c", "postgresql": "d79c3ac70093cb4cb531e71c", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -13829,8 +13829,8 @@ "databricks": "688179272da645629077e980", "duckdb": "688179272da645629077e980", "generic": "688179272da645629077e980", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "688179272da645629077e980", +"motherduck": "688179272da645629077e980", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "688179272da645629077e980", "postgresql": "688179272da645629077e980", "snowflake": "b210f8eb26c37b422f786c6b", @@ -13842,12 +13842,12 @@ { "DuckDB": "e08a957777bd278f921ca0ee", "None": "e08a957777bd278f921ca0ee", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "e08a957777bd278f921ca0ee", "duckdb": "e08a957777bd278f921ca0ee", "generic": "e08a957777bd278f921ca0ee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e08a957777bd278f921ca0ee", +"motherduck": "e08a957777bd278f921ca0ee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e08a957777bd278f921ca0ee", "postgresql": "e08a957777bd278f921ca0ee", "snowflake": "e08a957777bd278f921ca0ee", @@ -13860,11 +13860,11 @@ "DuckDB": "3533f91995bca270f191503d", "None": "3533f91995bca270f191503d", "bigquery": "3533f91995bca270f191503d", -"databricks": "fcfd1f4cfe959c3d88cf43c4", +"databricks": "3533f91995bca270f191503d", "duckdb": "3533f91995bca270f191503d", "generic": "3533f91995bca270f191503d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "3533f91995bca270f191503d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3533f91995bca270f191503d", "postgresql": "3533f91995bca270f191503d", "snowflake": "3533f91995bca270f191503d", @@ -13880,8 +13880,8 @@ "databricks": "c2700ca3573352821e09af03", "duckdb": "c2700ca3573352821e09af03", "generic": "c2700ca3573352821e09af03", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c2700ca3573352821e09af03", +"motherduck": "c2700ca3573352821e09af03", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c2700ca3573352821e09af03", "postgresql": "c2700ca3573352821e09af03", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -13897,10 +13897,10 @@ "databricks": "eea9a25a6fbf8e049a445513", "duckdb": "eea9a25a6fbf8e049a445513", "generic": "eea9a25a6fbf8e049a445513", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eea9a25a6fbf8e049a445513", -"postgres": "eea9a25a6fbf8e049a445513", -"postgresql": "eea9a25a6fbf8e049a445513", +"motherduck": "eea9a25a6fbf8e049a445513", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "769f762f4c7cffb2990ddacb", +"postgresql": "769f762f4c7cffb2990ddacb", "snowflake": "eea9a25a6fbf8e049a445513", "tsql": "eea9a25a6fbf8e049a445513" } @@ -13914,8 +13914,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b7d6fc051a3c1ea6deb29ff4", "postgresql": "b7d6fc051a3c1ea6deb29ff4", "snowflake": "251aba3fef503ce64556c701", @@ -13931,8 +13931,8 @@ "databricks": "1a804f317a3e9a0016925b04", "duckdb": "110327dc45eac0e8d54a96dc", "generic": "110327dc45eac0e8d54a96dc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1a804f317a3e9a0016925b04", +"motherduck": "110327dc45eac0e8d54a96dc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "110327dc45eac0e8d54a96dc", "postgresql": "110327dc45eac0e8d54a96dc", "snowflake": "110327dc45eac0e8d54a96dc", @@ -13948,8 +13948,8 @@ "databricks": "d6e37cd37419bfb26e09b080", "duckdb": "d6e37cd37419bfb26e09b080", "generic": "d6e37cd37419bfb26e09b080", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d6e37cd37419bfb26e09b080", +"motherduck": "d6e37cd37419bfb26e09b080", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d6e37cd37419bfb26e09b080", "postgresql": "d6e37cd37419bfb26e09b080", "snowflake": "d6e37cd37419bfb26e09b080", @@ -13961,14 +13961,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -13982,8 +13982,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -13999,8 +13999,8 @@ "databricks": "319a9fbb02ed503eb114164b", "duckdb": "e060b5a5a8e9774b580d58d3", "generic": "e060b5a5a8e9774b580d58d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "319a9fbb02ed503eb114164b", +"motherduck": "e060b5a5a8e9774b580d58d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e060b5a5a8e9774b580d58d3", "postgresql": "e060b5a5a8e9774b580d58d3", "snowflake": "e060b5a5a8e9774b580d58d3", @@ -14010,14 +14010,14 @@ [ "type", { -"DuckDB": "c6654395c62d21b415299f72", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "c6654395c62d21b415299f72", "bigquery": "0712c33a016f7dd6a37ee011", "databricks": "c6654395c62d21b415299f72", -"duckdb": "c6654395c62d21b415299f72", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "c6654395c62d21b415299f72", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2978a8fe925c7f93c6e9fcfb", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6654395c62d21b415299f72", "postgresql": "c6654395c62d21b415299f72", "snowflake": "0712c33a016f7dd6a37ee011", @@ -14028,17 +14028,17 @@ "type", { "DuckDB": "b0fba0f56f0cfe71621226b5", -"None": "b0fba0f56f0cfe71621226b5", -"bigquery": "f7fffd42e662ab4655dceb3b", -"databricks": "94b22f27d5086eaa9bb3eff5", +"None": "71ad5cceb4fd8e86446cf461", +"bigquery": "71ad5cceb4fd8e86446cf461", +"databricks": "71ad5cceb4fd8e86446cf461", "duckdb": "b0fba0f56f0cfe71621226b5", -"generic": "b0fba0f56f0cfe71621226b5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b0fba0f56f0cfe71621226b5", -"postgres": "b0fba0f56f0cfe71621226b5", -"postgresql": "b0fba0f56f0cfe71621226b5", +"generic": "71ad5cceb4fd8e86446cf461", +"motherduck": "b0fba0f56f0cfe71621226b5", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "71ad5cceb4fd8e86446cf461", +"postgresql": "71ad5cceb4fd8e86446cf461", "snowflake": "ffdce71ffba2fc524a0e9de5", -"tsql": "b0fba0f56f0cfe71621226b5" +"tsql": "71ad5cceb4fd8e86446cf461" } ], [ @@ -14050,10 +14050,10 @@ "databricks": "d227b99a82e505d599c2ed8a", "duckdb": "35a9c5c1419bff507cf65ae2", "generic": "d227b99a82e505d599c2ed8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d227b99a82e505d599c2ed8a", -"postgres": "d227b99a82e505d599c2ed8a", -"postgresql": "d227b99a82e505d599c2ed8a", +"motherduck": "35a9c5c1419bff507cf65ae2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "35a9c5c1419bff507cf65ae2", +"postgresql": "35a9c5c1419bff507cf65ae2", "snowflake": "d227b99a82e505d599c2ed8a", "tsql": "d227b99a82e505d599c2ed8a" } @@ -14067,8 +14067,8 @@ "databricks": "c2700ca3573352821e09af03", "duckdb": "c2700ca3573352821e09af03", "generic": "c2700ca3573352821e09af03", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c2700ca3573352821e09af03", +"motherduck": "c2700ca3573352821e09af03", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c2700ca3573352821e09af03", "postgresql": "c2700ca3573352821e09af03", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -14084,8 +14084,8 @@ "databricks": "3915f3f73328f0838497b2d5", "duckdb": "3915f3f73328f0838497b2d5", "generic": "3915f3f73328f0838497b2d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3915f3f73328f0838497b2d5", +"motherduck": "3915f3f73328f0838497b2d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3915f3f73328f0838497b2d5", "postgresql": "3915f3f73328f0838497b2d5", "snowflake": "f43dac38f34a1c0618a3f812", @@ -14101,8 +14101,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -14118,8 +14118,8 @@ "databricks": "b55b02912e30e27dcb40ad93", "duckdb": "b55b02912e30e27dcb40ad93", "generic": "b55b02912e30e27dcb40ad93", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b55b02912e30e27dcb40ad93", +"motherduck": "b55b02912e30e27dcb40ad93", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b55b02912e30e27dcb40ad93", "postgresql": "b55b02912e30e27dcb40ad93", "snowflake": "b55b02912e30e27dcb40ad93", @@ -14135,8 +14135,8 @@ "databricks": "3688fe25b8bdff7eb938e6f9", "duckdb": "e8cd7a4caa15ec2b6f990a1f", "generic": "1757eadf86c2a8956b520868", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3688fe25b8bdff7eb938e6f9", +"motherduck": "e8cd7a4caa15ec2b6f990a1f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8cd7a4caa15ec2b6f990a1f", "postgresql": "e8cd7a4caa15ec2b6f990a1f", "snowflake": "fe491937bc4ec7b061f2be43", @@ -14152,10 +14152,10 @@ "databricks": "c6106f30f23f392e17eadef4", "duckdb": "c6106f30f23f392e17eadef4", "generic": "c6106f30f23f392e17eadef4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6106f30f23f392e17eadef4", -"postgres": "c6106f30f23f392e17eadef4", -"postgresql": "c6106f30f23f392e17eadef4", +"motherduck": "c6106f30f23f392e17eadef4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f43dac38f34a1c0618a3f812", +"postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "c6106f30f23f392e17eadef4", "tsql": "c6106f30f23f392e17eadef4" } @@ -14165,12 +14165,12 @@ { "DuckDB": "eb9c0f675dd300eb82500b21", "None": "eb9c0f675dd300eb82500b21", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "eb9c0f675dd300eb82500b21", "duckdb": "eb9c0f675dd300eb82500b21", "generic": "eb9c0f675dd300eb82500b21", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eb9c0f675dd300eb82500b21", +"motherduck": "eb9c0f675dd300eb82500b21", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eb9c0f675dd300eb82500b21", "postgresql": "eb9c0f675dd300eb82500b21", "snowflake": "4e24a80b20b9dee0cbbfac17", @@ -14186,8 +14186,8 @@ "databricks": "3eb44072dafa3bef75c97b6a", "duckdb": "3eb44072dafa3bef75c97b6a", "generic": "3eb44072dafa3bef75c97b6a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3eb44072dafa3bef75c97b6a", +"motherduck": "3eb44072dafa3bef75c97b6a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3eb44072dafa3bef75c97b6a", "postgresql": "3eb44072dafa3bef75c97b6a", "snowflake": "3eb44072dafa3bef75c97b6a", @@ -14199,12 +14199,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -14215,17 +14215,17 @@ "type", { "DuckDB": "f2ed52f6bf46b4db9343551c", -"None": "f2ed52f6bf46b4db9343551c", -"bigquery": "f10bdd4d40eb495cbcecd31d", -"databricks": "f2ed52f6bf46b4db9343551c", +"None": "3f998a9ab68d8b2514840489", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "3f998a9ab68d8b2514840489", "duckdb": "f2ed52f6bf46b4db9343551c", -"generic": "f2ed52f6bf46b4db9343551c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f2ed52f6bf46b4db9343551c", -"postgres": "f2ed52f6bf46b4db9343551c", -"postgresql": "f2ed52f6bf46b4db9343551c", +"generic": "3f998a9ab68d8b2514840489", +"motherduck": "f2ed52f6bf46b4db9343551c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3f998a9ab68d8b2514840489", +"postgresql": "3f998a9ab68d8b2514840489", "snowflake": "f2ed52f6bf46b4db9343551c", -"tsql": "f2ed52f6bf46b4db9343551c" +"tsql": "3f998a9ab68d8b2514840489" } ], [ @@ -14237,10 +14237,10 @@ "databricks": "0960c90223e1eca15361360f", "duckdb": "0960c90223e1eca15361360f", "generic": "0960c90223e1eca15361360f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0960c90223e1eca15361360f", -"postgres": "0960c90223e1eca15361360f", -"postgresql": "0960c90223e1eca15361360f", +"motherduck": "0960c90223e1eca15361360f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b72367ce0764c938c620a44c", +"postgresql": "b72367ce0764c938c620a44c", "snowflake": "0960c90223e1eca15361360f", "tsql": "0960c90223e1eca15361360f" } @@ -14254,10 +14254,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -14271,8 +14271,8 @@ "databricks": "11d8d18c76077fddaf1b264f", "duckdb": "11d8d18c76077fddaf1b264f", "generic": "11d8d18c76077fddaf1b264f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "11d8d18c76077fddaf1b264f", +"motherduck": "11d8d18c76077fddaf1b264f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "11d8d18c76077fddaf1b264f", "postgresql": "11d8d18c76077fddaf1b264f", "snowflake": "11d8d18c76077fddaf1b264f", @@ -14288,8 +14288,8 @@ "databricks": "0b9d9879e289ea9a6a3e3e70", "duckdb": "0b9d9879e289ea9a6a3e3e70", "generic": "0b9d9879e289ea9a6a3e3e70", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0b9d9879e289ea9a6a3e3e70", +"motherduck": "0b9d9879e289ea9a6a3e3e70", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0b9d9879e289ea9a6a3e3e70", "postgresql": "0b9d9879e289ea9a6a3e3e70", "snowflake": "0b9d9879e289ea9a6a3e3e70", @@ -14305,8 +14305,8 @@ "databricks": "8e484ef176389314b9fd318e", "duckdb": "8e484ef176389314b9fd318e", "generic": "8e484ef176389314b9fd318e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8e484ef176389314b9fd318e", +"motherduck": "8e484ef176389314b9fd318e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8e484ef176389314b9fd318e", "postgresql": "8e484ef176389314b9fd318e", "snowflake": "8e484ef176389314b9fd318e", @@ -14322,8 +14322,8 @@ "databricks": "41a317d0625259b54aa1dc94", "duckdb": "41a317d0625259b54aa1dc94", "generic": "41a317d0625259b54aa1dc94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "41a317d0625259b54aa1dc94", +"motherduck": "41a317d0625259b54aa1dc94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "41a317d0625259b54aa1dc94", "postgresql": "41a317d0625259b54aa1dc94", "snowflake": "41a317d0625259b54aa1dc94", @@ -14335,12 +14335,12 @@ { "DuckDB": "f01435c3d6e460ffd86479dc", "None": "f01435c3d6e460ffd86479dc", -"bigquery": "99d6d9358db2ff349db17bb6", +"bigquery": "01d1b160c1768fa1cd4b2e45", "databricks": "72d1587b51c517b53f52a2d8", "duckdb": "f01435c3d6e460ffd86479dc", "generic": "f01435c3d6e460ffd86479dc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f01435c3d6e460ffd86479dc", +"motherduck": "f01435c3d6e460ffd86479dc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f01435c3d6e460ffd86479dc", "postgresql": "f01435c3d6e460ffd86479dc", "snowflake": "143dfed5733eda8a7f6ed075", @@ -14356,10 +14356,10 @@ "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", -"postgres": "5d25d5e13f81700741695ed4", -"postgresql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62131fb5b065f5de2e74a0f1", +"postgresql": "62131fb5b065f5de2e74a0f1", "snowflake": "5d25d5e13f81700741695ed4", "tsql": "5d25d5e13f81700741695ed4" } @@ -14368,17 +14368,17 @@ "type", { "DuckDB": "3e2cff9b165a6eaf1c043756", -"None": "77fe8e560c0d80d58e64a3ac", -"bigquery": "77fe8e560c0d80d58e64a3ac", -"databricks": "a8b5c14299c94dad25520df6", +"None": "ea6e350a4a788066ff386c50", +"bigquery": "80ea7a56a9546d00afc1689a", +"databricks": "ea6e350a4a788066ff386c50", "duckdb": "3e2cff9b165a6eaf1c043756", -"generic": "77fe8e560c0d80d58e64a3ac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "545b06c80ff7b812dafe5136", -"postgresql": "545b06c80ff7b812dafe5136", +"generic": "ea6e350a4a788066ff386c50", +"motherduck": "3e2cff9b165a6eaf1c043756", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ea6e350a4a788066ff386c50", +"postgresql": "ea6e350a4a788066ff386c50", "snowflake": "2a7a3265ae4026b435644d0e", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"tsql": "ea6e350a4a788066ff386c50" } ], [ @@ -14390,8 +14390,8 @@ "databricks": "adbb06204fd66d8071c3dbc6", "duckdb": "29739921db4d47abc1ac18c7", "generic": "29739921db4d47abc1ac18c7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "29739921db4d47abc1ac18c7", +"motherduck": "29739921db4d47abc1ac18c7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "29739921db4d47abc1ac18c7", "postgresql": "29739921db4d47abc1ac18c7", "snowflake": "216a7f0ead009a087eea4e1c", @@ -14407,8 +14407,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -14424,8 +14424,8 @@ "databricks": "f15c56475abfc73488abe63b", "duckdb": "f15c56475abfc73488abe63b", "generic": "f15c56475abfc73488abe63b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f15c56475abfc73488abe63b", +"motherduck": "f15c56475abfc73488abe63b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f15c56475abfc73488abe63b", "postgresql": "f15c56475abfc73488abe63b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -14441,10 +14441,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -14454,14 +14454,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -14475,8 +14475,8 @@ "databricks": "61058ba40f23266ededb040f", "duckdb": "53ea41ce10b9fa682ddf305d", "generic": "a1176e5228f8200c23e7a0a7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "53ea41ce10b9fa682ddf305d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a09c2bcd944462f73dd87c2d", "postgresql": "a09c2bcd944462f73dd87c2d", "snowflake": "9553636ef19ebb7706dabb5f", @@ -14492,10 +14492,10 @@ "databricks": "66078c43faec49916ed11546", "duckdb": "66078c43faec49916ed11546", "generic": "66078c43faec49916ed11546", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "66078c43faec49916ed11546", -"postgres": "66078c43faec49916ed11546", -"postgresql": "66078c43faec49916ed11546", +"motherduck": "66078c43faec49916ed11546", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "48a21c33337c0a03f7f0c746", +"postgresql": "48a21c33337c0a03f7f0c746", "snowflake": "66078c43faec49916ed11546", "tsql": "66078c43faec49916ed11546" } @@ -14509,8 +14509,8 @@ "databricks": "cea3ac691106cc77e45d9414", "duckdb": "cea3ac691106cc77e45d9414", "generic": "cea3ac691106cc77e45d9414", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cea3ac691106cc77e45d9414", +"motherduck": "cea3ac691106cc77e45d9414", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cea3ac691106cc77e45d9414", "postgresql": "cea3ac691106cc77e45d9414", "snowflake": "cea3ac691106cc77e45d9414", @@ -14522,12 +14522,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -14543,8 +14543,8 @@ "databricks": "937a574f550c49d90b6065cc", "duckdb": "937a574f550c49d90b6065cc", "generic": "937a574f550c49d90b6065cc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "937a574f550c49d90b6065cc", +"motherduck": "937a574f550c49d90b6065cc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "937a574f550c49d90b6065cc", "postgresql": "937a574f550c49d90b6065cc", "snowflake": "937a574f550c49d90b6065cc", @@ -14560,8 +14560,8 @@ "databricks": "ce156bc49d99761b01d1c550", "duckdb": "ce156bc49d99761b01d1c550", "generic": "ce156bc49d99761b01d1c550", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ce156bc49d99761b01d1c550", +"motherduck": "ce156bc49d99761b01d1c550", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ce156bc49d99761b01d1c550", "postgresql": "ce156bc49d99761b01d1c550", "snowflake": "ce156bc49d99761b01d1c550", @@ -14577,8 +14577,8 @@ "databricks": "2cc8cf0f153f72622d15048c", "duckdb": "2cc8cf0f153f72622d15048c", "generic": "2cc8cf0f153f72622d15048c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2cc8cf0f153f72622d15048c", +"motherduck": "2cc8cf0f153f72622d15048c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2cc8cf0f153f72622d15048c", "postgresql": "2cc8cf0f153f72622d15048c", "snowflake": "2cc8cf0f153f72622d15048c", @@ -14594,8 +14594,8 @@ "databricks": "ba72bc162859c1861cc127e7", "duckdb": "ba72bc162859c1861cc127e7", "generic": "ba72bc162859c1861cc127e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ba72bc162859c1861cc127e7", +"motherduck": "ba72bc162859c1861cc127e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ba72bc162859c1861cc127e7", "postgresql": "ba72bc162859c1861cc127e7", "snowflake": "ba72bc162859c1861cc127e7", @@ -14611,8 +14611,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -14628,8 +14628,8 @@ "databricks": "c2a3a3c3789aeea50e8c4f35", "duckdb": "c2a3a3c3789aeea50e8c4f35", "generic": "c2a3a3c3789aeea50e8c4f35", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c2a3a3c3789aeea50e8c4f35", +"motherduck": "c2a3a3c3789aeea50e8c4f35", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c2a3a3c3789aeea50e8c4f35", "postgresql": "c2a3a3c3789aeea50e8c4f35", "snowflake": "c2a3a3c3789aeea50e8c4f35", @@ -14645,10 +14645,10 @@ "databricks": "054898738769701df3abbf2e", "duckdb": "16c4b7f3bdc7c21a00a78838", "generic": "054898738769701df3abbf2e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "054898738769701df3abbf2e", -"postgres": "054898738769701df3abbf2e", -"postgresql": "054898738769701df3abbf2e", +"motherduck": "16c4b7f3bdc7c21a00a78838", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "16c4b7f3bdc7c21a00a78838", +"postgresql": "16c4b7f3bdc7c21a00a78838", "snowflake": "054898738769701df3abbf2e", "tsql": "054898738769701df3abbf2e" } @@ -14662,10 +14662,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "b0b09c4a6f7570ece56f59ce", "generic": "b0b09c4a6f7570ece56f59ce", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "355e032cc5c0f24695836b3d", -"postgres": "b0b09c4a6f7570ece56f59ce", -"postgresql": "b0b09c4a6f7570ece56f59ce", +"motherduck": "b0b09c4a6f7570ece56f59ce", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "355e032cc5c0f24695836b3d", "tsql": "b0b09c4a6f7570ece56f59ce" } @@ -14679,8 +14679,8 @@ "databricks": "e8a5e051bb567983c365b3fd", "duckdb": "e8a5e051bb567983c365b3fd", "generic": "e8a5e051bb567983c365b3fd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8a5e051bb567983c365b3fd", +"motherduck": "e8a5e051bb567983c365b3fd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8a5e051bb567983c365b3fd", "postgresql": "e8a5e051bb567983c365b3fd", "snowflake": "b210f8eb26c37b422f786c6b", @@ -14696,8 +14696,8 @@ "databricks": "207b6dc5f2a2547137393792", "duckdb": "50563fc19d4ed8ceb4f5e8e4", "generic": "50563fc19d4ed8ceb4f5e8e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "50563fc19d4ed8ceb4f5e8e4", +"motherduck": "50563fc19d4ed8ceb4f5e8e4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8770742a4fe26efe6bd47a00", "postgresql": "8770742a4fe26efe6bd47a00", "snowflake": "a4cd3eca76c356ecd633fe7b", @@ -14713,10 +14713,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -14730,8 +14730,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -14747,8 +14747,8 @@ "databricks": "77ffdb80029bd6e2cca768ad", "duckdb": "77ffdb80029bd6e2cca768ad", "generic": "77ffdb80029bd6e2cca768ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "77ffdb80029bd6e2cca768ad", +"motherduck": "77ffdb80029bd6e2cca768ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "77ffdb80029bd6e2cca768ad", "postgresql": "77ffdb80029bd6e2cca768ad", "snowflake": "77ffdb80029bd6e2cca768ad", @@ -14764,8 +14764,8 @@ "databricks": "c37de6584758b2433c289aee", "duckdb": "c37de6584758b2433c289aee", "generic": "c37de6584758b2433c289aee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c37de6584758b2433c289aee", +"motherduck": "c37de6584758b2433c289aee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c37de6584758b2433c289aee", "postgresql": "c37de6584758b2433c289aee", "snowflake": "c37de6584758b2433c289aee", @@ -14781,8 +14781,8 @@ "databricks": "555111267818cbd3d0b07105", "duckdb": "555111267818cbd3d0b07105", "generic": "555111267818cbd3d0b07105", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "555111267818cbd3d0b07105", +"motherduck": "555111267818cbd3d0b07105", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "555111267818cbd3d0b07105", "postgresql": "555111267818cbd3d0b07105", "snowflake": "555111267818cbd3d0b07105", @@ -14798,8 +14798,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -14815,10 +14815,10 @@ "databricks": "3c0719ca5c83e0a20d0b0b42", "duckdb": "42d4a23d9a52e79d0c3458d3", "generic": "3c0719ca5c83e0a20d0b0b42", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3c0719ca5c83e0a20d0b0b42", -"postgres": "3c0719ca5c83e0a20d0b0b42", -"postgresql": "3c0719ca5c83e0a20d0b0b42", +"motherduck": "42d4a23d9a52e79d0c3458d3", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "42d4a23d9a52e79d0c3458d3", +"postgresql": "42d4a23d9a52e79d0c3458d3", "snowflake": "a3acd7ba35e81a800f967bd3", "tsql": "3c0719ca5c83e0a20d0b0b42" } @@ -14828,12 +14828,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -14849,8 +14849,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -14861,34 +14861,34 @@ "type", { "DuckDB": "919b7ec6a7b49d8527b784cd", -"None": "919b7ec6a7b49d8527b784cd", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "e3bc1b8bad22d8104417b00f", +"None": "558da38ec4d9d5d6c72f60cf", +"bigquery": "c50a316d091c821866e4bfba", +"databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "919b7ec6a7b49d8527b784cd", -"generic": "919b7ec6a7b49d8527b784cd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "919b7ec6a7b49d8527b784cd", -"postgres": "a3b6b25265be330ec52a3322", -"postgresql": "a3b6b25265be330ec52a3322", +"generic": "558da38ec4d9d5d6c72f60cf", +"motherduck": "919b7ec6a7b49d8527b784cd", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "558da38ec4d9d5d6c72f60cf", +"postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "919b7ec6a7b49d8527b784cd", -"tsql": "919b7ec6a7b49d8527b784cd" +"tsql": "558da38ec4d9d5d6c72f60cf" } ], [ "type", { "DuckDB": "a428302e4848ffe877284c84", -"None": "a428302e4848ffe877284c84", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "a428302e4848ffe877284c84", +"None": "c9b810639795a7bfab91b98d", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "c9b810639795a7bfab91b98d", "duckdb": "a428302e4848ffe877284c84", -"generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"generic": "c9b810639795a7bfab91b98d", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "a428302e4848ffe877284c84" +"tsql": "c9b810639795a7bfab91b98d" } ], [ @@ -14900,8 +14900,8 @@ "databricks": "5fd024c2193505b62de9c162", "duckdb": "5fd024c2193505b62de9c162", "generic": "5fd024c2193505b62de9c162", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5fd024c2193505b62de9c162", +"motherduck": "5fd024c2193505b62de9c162", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5fd024c2193505b62de9c162", "postgresql": "5fd024c2193505b62de9c162", "snowflake": "5fd024c2193505b62de9c162", @@ -14913,14 +14913,14 @@ { "DuckDB": "4b356542044f1267fcfc487a", "None": "4b356542044f1267fcfc487a", -"bigquery": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", "databricks": "4b356542044f1267fcfc487a", "duckdb": "4b356542044f1267fcfc487a", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "4b356542044f1267fcfc487a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -14934,8 +14934,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -14951,8 +14951,8 @@ "databricks": "6bd27be6a47ef34ec1defbad", "duckdb": "b0791f9f270e764eeeb6fb8e", "generic": "c7c38fdeaeb7858a44976f1d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c7c38fdeaeb7858a44976f1d", +"motherduck": "b0791f9f270e764eeeb6fb8e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4d7604d41e3585ec187dec82", "postgresql": "4d7604d41e3585ec187dec82", "snowflake": "c7c38fdeaeb7858a44976f1d", @@ -14968,8 +14968,8 @@ "databricks": "66bffd006fc7638db82257d2", "duckdb": "66bffd006fc7638db82257d2", "generic": "66bffd006fc7638db82257d2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "66bffd006fc7638db82257d2", +"motherduck": "66bffd006fc7638db82257d2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66bffd006fc7638db82257d2", "postgresql": "66bffd006fc7638db82257d2", "snowflake": "66bffd006fc7638db82257d2", @@ -14985,8 +14985,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -14998,14 +14998,14 @@ { "DuckDB": "5d25d5e13f81700741695ed4", "None": "5d25d5e13f81700741695ed4", -"bigquery": "23ee196b7bd6039855a1460f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", -"postgres": "5d25d5e13f81700741695ed4", -"postgresql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62131fb5b065f5de2e74a0f1", +"postgresql": "62131fb5b065f5de2e74a0f1", "snowflake": "5d25d5e13f81700741695ed4", "tsql": "5d25d5e13f81700741695ed4" } @@ -15019,8 +15019,8 @@ "databricks": "52928fec41d9097d054cb63d", "duckdb": "52928fec41d9097d054cb63d", "generic": "52928fec41d9097d054cb63d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "52928fec41d9097d054cb63d", +"motherduck": "52928fec41d9097d054cb63d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "52928fec41d9097d054cb63d", "postgresql": "52928fec41d9097d054cb63d", "snowflake": "52928fec41d9097d054cb63d", @@ -15032,14 +15032,14 @@ { "DuckDB": "b561884132dbe7e42b719110", "None": "b561884132dbe7e42b719110", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -15047,14 +15047,14 @@ [ "type", { -"DuckDB": "c6654395c62d21b415299f72", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "c6654395c62d21b415299f72", "bigquery": "0712c33a016f7dd6a37ee011", "databricks": "c6654395c62d21b415299f72", -"duckdb": "c6654395c62d21b415299f72", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "c6654395c62d21b415299f72", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2978a8fe925c7f93c6e9fcfb", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6654395c62d21b415299f72", "postgresql": "c6654395c62d21b415299f72", "snowflake": "0712c33a016f7dd6a37ee011", @@ -15066,12 +15066,12 @@ { "DuckDB": "2ffee1a4ca7dc4a711fc0d27", "None": "2ffee1a4ca7dc4a711fc0d27", -"bigquery": "2ffee1a4ca7dc4a711fc0d27", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "2ffee1a4ca7dc4a711fc0d27", "duckdb": "2ffee1a4ca7dc4a711fc0d27", "generic": "2ffee1a4ca7dc4a711fc0d27", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2ffee1a4ca7dc4a711fc0d27", +"motherduck": "2ffee1a4ca7dc4a711fc0d27", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2ffee1a4ca7dc4a711fc0d27", "postgresql": "2ffee1a4ca7dc4a711fc0d27", "snowflake": "2ffee1a4ca7dc4a711fc0d27", @@ -15087,8 +15087,8 @@ "databricks": "c6889f2965db73017be6f6e7", "duckdb": "c6889f2965db73017be6f6e7", "generic": "c6889f2965db73017be6f6e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6889f2965db73017be6f6e7", +"motherduck": "c6889f2965db73017be6f6e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6889f2965db73017be6f6e7", "postgresql": "c6889f2965db73017be6f6e7", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -15104,10 +15104,10 @@ "databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -15121,8 +15121,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -15135,11 +15135,11 @@ "DuckDB": "9c2cff8be7225cf79883ca65", "None": "9c2cff8be7225cf79883ca65", "bigquery": "9c2cff8be7225cf79883ca65", -"databricks": "901ac89f8d9ec18bd3a61e4b", +"databricks": "9c2cff8be7225cf79883ca65", "duckdb": "9c2cff8be7225cf79883ca65", "generic": "9c2cff8be7225cf79883ca65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "901ac89f8d9ec18bd3a61e4b", +"motherduck": "9c2cff8be7225cf79883ca65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9c2cff8be7225cf79883ca65", "postgresql": "9c2cff8be7225cf79883ca65", "snowflake": "9c2cff8be7225cf79883ca65", @@ -15155,10 +15155,10 @@ "databricks": "ab62f43260f32e2cf62666fc", "duckdb": "0baaca265927140414f32b37", "generic": "ab62f43260f32e2cf62666fc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab62f43260f32e2cf62666fc", -"postgres": "ab62f43260f32e2cf62666fc", -"postgresql": "ab62f43260f32e2cf62666fc", +"motherduck": "0baaca265927140414f32b37", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9ea4932505663b43f527adad", +"postgresql": "9ea4932505663b43f527adad", "snowflake": "ab62f43260f32e2cf62666fc", "tsql": "ab62f43260f32e2cf62666fc" } @@ -15172,8 +15172,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -15185,12 +15185,12 @@ { "DuckDB": "97e5335218efead1a4226b8d", "None": "97e5335218efead1a4226b8d", -"bigquery": "97e5335218efead1a4226b8d", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "97e5335218efead1a4226b8d", "duckdb": "97e5335218efead1a4226b8d", "generic": "97e5335218efead1a4226b8d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "97e5335218efead1a4226b8d", +"motherduck": "97e5335218efead1a4226b8d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "97e5335218efead1a4226b8d", "postgresql": "97e5335218efead1a4226b8d", "snowflake": "97e5335218efead1a4226b8d", @@ -15202,14 +15202,14 @@ { "DuckDB": "ee47e1424cb01deb0cf51760", "None": "ee47e1424cb01deb0cf51760", -"bigquery": "ee47e1424cb01deb0cf51760", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "ee47e1424cb01deb0cf51760", "duckdb": "ee47e1424cb01deb0cf51760", "generic": "ee47e1424cb01deb0cf51760", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ee47e1424cb01deb0cf51760", -"postgres": "ee47e1424cb01deb0cf51760", -"postgresql": "ee47e1424cb01deb0cf51760", +"motherduck": "ee47e1424cb01deb0cf51760", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3c1521654088dd54d0dc9ead", +"postgresql": "3c1521654088dd54d0dc9ead", "snowflake": "ee47e1424cb01deb0cf51760", "tsql": "ee47e1424cb01deb0cf51760" } @@ -15219,12 +15219,12 @@ { "DuckDB": "2d4aea8dec5521e258596f3d", "None": "2d4aea8dec5521e258596f3d", -"bigquery": "2d4aea8dec5521e258596f3d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "2d4aea8dec5521e258596f3d", "duckdb": "2d4aea8dec5521e258596f3d", "generic": "2d4aea8dec5521e258596f3d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2d4aea8dec5521e258596f3d", +"motherduck": "2d4aea8dec5521e258596f3d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f15c56475abfc73488abe63b", "postgresql": "f15c56475abfc73488abe63b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -15240,8 +15240,8 @@ "databricks": "8917b70db141a02ac2e2bb1e", "duckdb": "8917b70db141a02ac2e2bb1e", "generic": "8917b70db141a02ac2e2bb1e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8917b70db141a02ac2e2bb1e", +"motherduck": "8917b70db141a02ac2e2bb1e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8917b70db141a02ac2e2bb1e", "postgresql": "8917b70db141a02ac2e2bb1e", "snowflake": "8917b70db141a02ac2e2bb1e", @@ -15257,8 +15257,8 @@ "databricks": "ef480cafe4d260b70ce5214c", "duckdb": "ef480cafe4d260b70ce5214c", "generic": "ef480cafe4d260b70ce5214c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ef480cafe4d260b70ce5214c", +"motherduck": "ef480cafe4d260b70ce5214c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ef480cafe4d260b70ce5214c", "postgresql": "ef480cafe4d260b70ce5214c", "snowflake": "ef480cafe4d260b70ce5214c", @@ -15269,17 +15269,17 @@ "type", { "DuckDB": "19bdd92ea0cc5fb028657510", -"None": "78b2cf275c640af54feeca31", -"bigquery": "1e75e5227bea0ae294ac7af1", -"databricks": "1e75e5227bea0ae294ac7af1", +"None": "8075663faec01d3abb4fc153", +"bigquery": "8075663faec01d3abb4fc153", +"databricks": "8075663faec01d3abb4fc153", "duckdb": "19bdd92ea0cc5fb028657510", -"generic": "78b2cf275c640af54feeca31", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", -"postgres": "78b2cf275c640af54feeca31", -"postgresql": "78b2cf275c640af54feeca31", +"generic": "8075663faec01d3abb4fc153", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8075663faec01d3abb4fc153", +"postgresql": "8075663faec01d3abb4fc153", "snowflake": "8ff614b7306c0edbe36ce00b", -"tsql": "78b2cf275c640af54feeca31" +"tsql": "8075663faec01d3abb4fc153" } ], [ @@ -15287,14 +15287,14 @@ { "DuckDB": "ee47e1424cb01deb0cf51760", "None": "ee47e1424cb01deb0cf51760", -"bigquery": "ee47e1424cb01deb0cf51760", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "ee47e1424cb01deb0cf51760", "duckdb": "ee47e1424cb01deb0cf51760", "generic": "ee47e1424cb01deb0cf51760", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ee47e1424cb01deb0cf51760", -"postgres": "ee47e1424cb01deb0cf51760", -"postgresql": "ee47e1424cb01deb0cf51760", +"motherduck": "ee47e1424cb01deb0cf51760", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3c1521654088dd54d0dc9ead", +"postgresql": "3c1521654088dd54d0dc9ead", "snowflake": "ee47e1424cb01deb0cf51760", "tsql": "ee47e1424cb01deb0cf51760" } @@ -15308,8 +15308,8 @@ "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aab0389e2b5470136660bf8a", "postgresql": "aab0389e2b5470136660bf8a", "snowflake": "97a20344d811fb77c7690863", @@ -15321,14 +15321,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -15342,10 +15342,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "61d08c878cc506dd91ca4586", "generic": "61d08c878cc506dd91ca4586", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c100cdbd268164cf084295be", -"postgres": "61d08c878cc506dd91ca4586", -"postgresql": "61d08c878cc506dd91ca4586", +"motherduck": "61d08c878cc506dd91ca4586", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "c100cdbd268164cf084295be", "tsql": "61d08c878cc506dd91ca4586" } @@ -15359,8 +15359,8 @@ "databricks": "41ae0d23941c87be23d59e1f", "duckdb": "755657fbcced1de4551fb370", "generic": "41ae0d23941c87be23d59e1f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "41ae0d23941c87be23d59e1f", +"motherduck": "755657fbcced1de4551fb370", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "41ae0d23941c87be23d59e1f", "postgresql": "41ae0d23941c87be23d59e1f", "snowflake": "41ae0d23941c87be23d59e1f", @@ -15376,8 +15376,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -15393,8 +15393,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "251aba3fef503ce64556c701", "postgresql": "251aba3fef503ce64556c701", "snowflake": "c50a316d091c821866e4bfba", @@ -15410,8 +15410,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -15427,8 +15427,8 @@ "databricks": "c82815abe30b3d099903f838", "duckdb": "c82815abe30b3d099903f838", "generic": "c82815abe30b3d099903f838", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c82815abe30b3d099903f838", +"motherduck": "c82815abe30b3d099903f838", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c82815abe30b3d099903f838", "postgresql": "c82815abe30b3d099903f838", "snowflake": "c82815abe30b3d099903f838", @@ -15444,8 +15444,8 @@ "databricks": "d6e37cd37419bfb26e09b080", "duckdb": "d6e37cd37419bfb26e09b080", "generic": "d6e37cd37419bfb26e09b080", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d6e37cd37419bfb26e09b080", +"motherduck": "d6e37cd37419bfb26e09b080", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d6e37cd37419bfb26e09b080", "postgresql": "d6e37cd37419bfb26e09b080", "snowflake": "d6e37cd37419bfb26e09b080", @@ -15457,12 +15457,12 @@ { "DuckDB": "5c28019dce48a74708c90a76", "None": "5c28019dce48a74708c90a76", -"bigquery": "f28a96f0eb38ad8874f6ec5d", +"bigquery": "5c28019dce48a74708c90a76", "databricks": "5c28019dce48a74708c90a76", "duckdb": "5c28019dce48a74708c90a76", "generic": "5c28019dce48a74708c90a76", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "5c28019dce48a74708c90a76", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c28019dce48a74708c90a76", "postgresql": "5c28019dce48a74708c90a76", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -15478,8 +15478,8 @@ "databricks": "f4c43a87a78970bca1f88b09", "duckdb": "f4c43a87a78970bca1f88b09", "generic": "f4c43a87a78970bca1f88b09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4c43a87a78970bca1f88b09", +"motherduck": "f4c43a87a78970bca1f88b09", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c43a87a78970bca1f88b09", "postgresql": "f4c43a87a78970bca1f88b09", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -15495,10 +15495,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -15512,8 +15512,8 @@ "databricks": "9070740b5c7f7fb5d091ad05", "duckdb": "9070740b5c7f7fb5d091ad05", "generic": "9070740b5c7f7fb5d091ad05", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9070740b5c7f7fb5d091ad05", +"motherduck": "9070740b5c7f7fb5d091ad05", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9070740b5c7f7fb5d091ad05", "postgresql": "9070740b5c7f7fb5d091ad05", "snowflake": "9070740b5c7f7fb5d091ad05", @@ -15529,10 +15529,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -15546,8 +15546,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -15558,15 +15558,15 @@ "type", { "DuckDB": "c0eec31620dfc2ac0028173d", -"None": "7f902bdd94be27212c27de4d", -"bigquery": "02e0b2ed25f32a8d09350a23", -"databricks": "7f902bdd94be27212c27de4d", +"None": "bf34c743a59d7c3f212cf17b", +"bigquery": "bf34c743a59d7c3f212cf17b", +"databricks": "bf34c743a59d7c3f212cf17b", "duckdb": "c0eec31620dfc2ac0028173d", -"generic": "7f902bdd94be27212c27de4d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "6d562c54c27c6810625f7693", -"postgresql": "6d562c54c27c6810625f7693", +"generic": "bf34c743a59d7c3f212cf17b", +"motherduck": "c0eec31620dfc2ac0028173d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "759df854baf67e256ec19adb", +"postgresql": "759df854baf67e256ec19adb", "snowflake": "354dee69d4f0c63cf79046ac", "tsql": "bf34c743a59d7c3f212cf17b" } @@ -15580,8 +15580,8 @@ "databricks": "6cffd1617edd6454241ee732", "duckdb": "6cffd1617edd6454241ee732", "generic": "6cffd1617edd6454241ee732", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6cffd1617edd6454241ee732", +"motherduck": "6cffd1617edd6454241ee732", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6cffd1617edd6454241ee732", "postgresql": "6cffd1617edd6454241ee732", "snowflake": "6cffd1617edd6454241ee732", @@ -15597,8 +15597,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "111af35848fe500ec2ae54b2", "generic": "111af35848fe500ec2ae54b2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e3bc1b8bad22d8104417b00f", +"motherduck": "111af35848fe500ec2ae54b2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "111af35848fe500ec2ae54b2", "postgresql": "111af35848fe500ec2ae54b2", "snowflake": "e3bc1b8bad22d8104417b00f", @@ -15614,8 +15614,8 @@ "databricks": "e4f0244beaa01667e80482b6", "duckdb": "a766fea06fc070b352976b0a", "generic": "a766fea06fc070b352976b0a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e4f0244beaa01667e80482b6", +"motherduck": "a766fea06fc070b352976b0a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a766fea06fc070b352976b0a", "postgresql": "a766fea06fc070b352976b0a", "snowflake": "a766fea06fc070b352976b0a", @@ -15631,8 +15631,8 @@ "databricks": "6a912271e85e6d85c1238bc0", "duckdb": "6a912271e85e6d85c1238bc0", "generic": "6a912271e85e6d85c1238bc0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6a912271e85e6d85c1238bc0", +"motherduck": "6a912271e85e6d85c1238bc0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6a912271e85e6d85c1238bc0", "postgresql": "6a912271e85e6d85c1238bc0", "snowflake": "6a912271e85e6d85c1238bc0", @@ -15648,10 +15648,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "3efe8bb57c98fcbe74096b3c", "generic": "3efe8bb57c98fcbe74096b3c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1d8b945fd793ecdcacb90c21", -"postgres": "3efe8bb57c98fcbe74096b3c", -"postgresql": "3efe8bb57c98fcbe74096b3c", +"motherduck": "3efe8bb57c98fcbe74096b3c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "1d8b945fd793ecdcacb90c21", "tsql": "3efe8bb57c98fcbe74096b3c" } @@ -15660,17 +15660,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -15682,10 +15682,10 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "b8321619b794db091c9e96a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b8321619b794db091c9e96a1", -"postgres": "b8321619b794db091c9e96a1", -"postgresql": "b8321619b794db091c9e96a1", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bce26544873d6ec9191c9a86", +"postgresql": "bce26544873d6ec9191c9a86", "snowflake": "b8321619b794db091c9e96a1", "tsql": "b8321619b794db091c9e96a1" } @@ -15694,17 +15694,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -15716,8 +15716,8 @@ "databricks": "8a3f094f18fb4047f0febf59", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "8a3f094f18fb4047f0febf59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a3f094f18fb4047f0febf59", "postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "850deea990b52b82b36a9848", @@ -15733,8 +15733,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -15746,12 +15746,12 @@ { "DuckDB": "07c9e779f7ae9c58f4f0c902", "None": "07c9e779f7ae9c58f4f0c902", -"bigquery": "07c9e779f7ae9c58f4f0c902", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "07c9e779f7ae9c58f4f0c902", "duckdb": "07c9e779f7ae9c58f4f0c902", "generic": "07c9e779f7ae9c58f4f0c902", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "07c9e779f7ae9c58f4f0c902", +"motherduck": "07c9e779f7ae9c58f4f0c902", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "07c9e779f7ae9c58f4f0c902", "postgresql": "07c9e779f7ae9c58f4f0c902", "snowflake": "07c9e779f7ae9c58f4f0c902", @@ -15767,8 +15767,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -15780,12 +15780,12 @@ { "DuckDB": "5c28019dce48a74708c90a76", "None": "5c28019dce48a74708c90a76", -"bigquery": "f28a96f0eb38ad8874f6ec5d", +"bigquery": "5c28019dce48a74708c90a76", "databricks": "5c28019dce48a74708c90a76", "duckdb": "5c28019dce48a74708c90a76", "generic": "5c28019dce48a74708c90a76", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "5c28019dce48a74708c90a76", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c28019dce48a74708c90a76", "postgresql": "5c28019dce48a74708c90a76", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -15801,8 +15801,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -15814,12 +15814,12 @@ { "DuckDB": "e248573edaaaa875cb926c45", "None": "e248573edaaaa875cb926c45", -"bigquery": "2a0918be53051fe629d9ff8f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "e248573edaaaa875cb926c45", "duckdb": "e248573edaaaa875cb926c45", "generic": "e248573edaaaa875cb926c45", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e248573edaaaa875cb926c45", +"motherduck": "e248573edaaaa875cb926c45", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e248573edaaaa875cb926c45", "postgresql": "e248573edaaaa875cb926c45", "snowflake": "e248573edaaaa875cb926c45", @@ -15835,8 +15835,8 @@ "databricks": "0b8ad171901cc2b7725617da", "duckdb": "0b8ad171901cc2b7725617da", "generic": "0b8ad171901cc2b7725617da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0b8ad171901cc2b7725617da", +"motherduck": "0b8ad171901cc2b7725617da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0b8ad171901cc2b7725617da", "postgresql": "0b8ad171901cc2b7725617da", "snowflake": "0b8ad171901cc2b7725617da", @@ -15848,12 +15848,12 @@ { "DuckDB": "d44c023c720f385769f2ab81", "None": "d44c023c720f385769f2ab81", -"bigquery": "d44c023c720f385769f2ab81", +"bigquery": "c114794e288b3464c914fd5c", "databricks": "d44c023c720f385769f2ab81", "duckdb": "d44c023c720f385769f2ab81", "generic": "d44c023c720f385769f2ab81", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d44c023c720f385769f2ab81", +"motherduck": "d44c023c720f385769f2ab81", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d44c023c720f385769f2ab81", "postgresql": "d44c023c720f385769f2ab81", "snowflake": "d44c023c720f385769f2ab81", @@ -15869,8 +15869,8 @@ "databricks": "8a3f094f18fb4047f0febf59", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "8a3f094f18fb4047f0febf59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a3f094f18fb4047f0febf59", "postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "850deea990b52b82b36a9848", @@ -15886,8 +15886,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -15903,8 +15903,8 @@ "databricks": "c5c5fbb0257c93153cb84430", "duckdb": "c5c5fbb0257c93153cb84430", "generic": "c5c5fbb0257c93153cb84430", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c5c5fbb0257c93153cb84430", +"motherduck": "c5c5fbb0257c93153cb84430", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c5c5fbb0257c93153cb84430", "postgresql": "c5c5fbb0257c93153cb84430", "snowflake": "c5c5fbb0257c93153cb84430", @@ -15920,8 +15920,8 @@ "databricks": "d6e37cd37419bfb26e09b080", "duckdb": "d6e37cd37419bfb26e09b080", "generic": "d6e37cd37419bfb26e09b080", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d6e37cd37419bfb26e09b080", +"motherduck": "d6e37cd37419bfb26e09b080", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d6e37cd37419bfb26e09b080", "postgresql": "d6e37cd37419bfb26e09b080", "snowflake": "d6e37cd37419bfb26e09b080", @@ -15934,11 +15934,11 @@ "DuckDB": "d34f84c1b71b9c9c0c20abb0", "None": "d34f84c1b71b9c9c0c20abb0", "bigquery": "d34f84c1b71b9c9c0c20abb0", -"databricks": "c662d9470aa4255dfb493df1", +"databricks": "d34f84c1b71b9c9c0c20abb0", "duckdb": "d34f84c1b71b9c9c0c20abb0", "generic": "d34f84c1b71b9c9c0c20abb0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c662d9470aa4255dfb493df1", +"motherduck": "d34f84c1b71b9c9c0c20abb0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d34f84c1b71b9c9c0c20abb0", "postgresql": "d34f84c1b71b9c9c0c20abb0", "snowflake": "d34f84c1b71b9c9c0c20abb0", @@ -15949,17 +15949,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -15971,8 +15971,8 @@ "databricks": "bbb2563edda71d0b69ef0855", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "bbb2563edda71d0b69ef0855", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "51d717b08676179ee1ccefe4", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "bbb2563edda71d0b69ef0855", "postgresql": "bbb2563edda71d0b69ef0855", "snowflake": "bbb2563edda71d0b69ef0855", @@ -15988,8 +15988,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -16005,8 +16005,8 @@ "databricks": "c1eef7499ef79562fa125447", "duckdb": "c1eef7499ef79562fa125447", "generic": "c1eef7499ef79562fa125447", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c1eef7499ef79562fa125447", +"motherduck": "c1eef7499ef79562fa125447", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c1eef7499ef79562fa125447", "postgresql": "c1eef7499ef79562fa125447", "snowflake": "c1eef7499ef79562fa125447", @@ -16022,8 +16022,8 @@ "databricks": "b210f8eb26c37b422f786c6b", "duckdb": "b210f8eb26c37b422f786c6b", "generic": "b210f8eb26c37b422f786c6b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b210f8eb26c37b422f786c6b", +"motherduck": "b210f8eb26c37b422f786c6b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b210f8eb26c37b422f786c6b", "postgresql": "b210f8eb26c37b422f786c6b", "snowflake": "b210f8eb26c37b422f786c6b", @@ -16035,12 +16035,12 @@ { "DuckDB": "2d4aea8dec5521e258596f3d", "None": "2d4aea8dec5521e258596f3d", -"bigquery": "2d4aea8dec5521e258596f3d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "2d4aea8dec5521e258596f3d", "duckdb": "2d4aea8dec5521e258596f3d", "generic": "2d4aea8dec5521e258596f3d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2d4aea8dec5521e258596f3d", +"motherduck": "2d4aea8dec5521e258596f3d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f15c56475abfc73488abe63b", "postgresql": "f15c56475abfc73488abe63b", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -16050,16 +16050,16 @@ [ "type", { -"DuckDB": "b4d65f2c9d0405e88d28938b", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "ba98b19402ac6379a4172de9", "bigquery": "ba98b19402ac6379a4172de9", "databricks": "ba98b19402ac6379a4172de9", -"duckdb": "b4d65f2c9d0405e88d28938b", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "ba98b19402ac6379a4172de9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ba98b19402ac6379a4172de9", -"postgres": "ba98b19402ac6379a4172de9", -"postgresql": "ba98b19402ac6379a4172de9", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3aa01d5ccf0d0b7e5c540297", +"postgresql": "3aa01d5ccf0d0b7e5c540297", "snowflake": "ba98b19402ac6379a4172de9", "tsql": "ba98b19402ac6379a4172de9" } @@ -16069,14 +16069,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -16085,17 +16085,17 @@ "type", { "DuckDB": "6c58c6a62958e64e6a26bf9c", -"None": "6c58c6a62958e64e6a26bf9c", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "6c58c6a62958e64e6a26bf9c", +"None": "9572f907cb1c76265b3b608f", +"bigquery": "9572f907cb1c76265b3b608f", +"databricks": "9572f907cb1c76265b3b608f", "duckdb": "6c58c6a62958e64e6a26bf9c", -"generic": "6c58c6a62958e64e6a26bf9c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c58c6a62958e64e6a26bf9c", -"postgres": "571886088d0423ed1a0f1696", -"postgresql": "571886088d0423ed1a0f1696", +"generic": "9572f907cb1c76265b3b608f", +"motherduck": "6c58c6a62958e64e6a26bf9c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9572f907cb1c76265b3b608f", +"postgresql": "9572f907cb1c76265b3b608f", "snowflake": "6c58c6a62958e64e6a26bf9c", -"tsql": "919b7ec6a7b49d8527b784cd" +"tsql": "9572f907cb1c76265b3b608f" } ], [ @@ -16107,8 +16107,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -16124,8 +16124,8 @@ "databricks": "42a76d5ff05d0f91911ff368", "duckdb": "42a76d5ff05d0f91911ff368", "generic": "42a76d5ff05d0f91911ff368", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "42a76d5ff05d0f91911ff368", +"motherduck": "42a76d5ff05d0f91911ff368", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "42a76d5ff05d0f91911ff368", "postgresql": "42a76d5ff05d0f91911ff368", "snowflake": "42a76d5ff05d0f91911ff368", @@ -16141,8 +16141,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "251aba3fef503ce64556c701", "postgresql": "251aba3fef503ce64556c701", "snowflake": "c50a316d091c821866e4bfba", @@ -16154,12 +16154,12 @@ { "DuckDB": "5d25d5e13f81700741695ed4", "None": "5d25d5e13f81700741695ed4", -"bigquery": "23ee196b7bd6039855a1460f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5d25d5e13f81700741695ed4", "postgresql": "5d25d5e13f81700741695ed4", "snowflake": "5d25d5e13f81700741695ed4", @@ -16175,8 +16175,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "1e75e5227bea0ae294ac7af1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19bdd92ea0cc5fb028657510", "postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -16192,8 +16192,8 @@ "databricks": "268b9b7c7862c9de6569286a", "duckdb": "268b9b7c7862c9de6569286a", "generic": "268b9b7c7862c9de6569286a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "268b9b7c7862c9de6569286a", +"motherduck": "268b9b7c7862c9de6569286a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "268b9b7c7862c9de6569286a", "postgresql": "268b9b7c7862c9de6569286a", "snowflake": "268b9b7c7862c9de6569286a", @@ -16209,8 +16209,8 @@ "databricks": "da33de41a707d0e067e415fd", "duckdb": "da33de41a707d0e067e415fd", "generic": "da33de41a707d0e067e415fd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "da33de41a707d0e067e415fd", +"motherduck": "da33de41a707d0e067e415fd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "da33de41a707d0e067e415fd", "postgresql": "da33de41a707d0e067e415fd", "snowflake": "da33de41a707d0e067e415fd", @@ -16226,8 +16226,8 @@ "databricks": "7c8df74df226e73b459bfdba", "duckdb": "7c8df74df226e73b459bfdba", "generic": "7c8df74df226e73b459bfdba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7c8df74df226e73b459bfdba", +"motherduck": "7c8df74df226e73b459bfdba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7c8df74df226e73b459bfdba", "postgresql": "7c8df74df226e73b459bfdba", "snowflake": "7c8df74df226e73b459bfdba", @@ -16243,8 +16243,8 @@ "databricks": "ebb346128c80cb653e9694c8", "duckdb": "ebb346128c80cb653e9694c8", "generic": "ebb346128c80cb653e9694c8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebb346128c80cb653e9694c8", +"motherduck": "ebb346128c80cb653e9694c8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebb346128c80cb653e9694c8", "postgresql": "ebb346128c80cb653e9694c8", "snowflake": "ebb346128c80cb653e9694c8", @@ -16255,17 +16255,17 @@ "type", { "DuckDB": "9f4b51f000ffbe30f6b7d826", -"None": "bacfe825b19ef52bc792ab22", +"None": "00714a615c9e7a090fb122c5", "bigquery": "00714a615c9e7a090fb122c5", "databricks": "00714a615c9e7a090fb122c5", "duckdb": "9f4b51f000ffbe30f6b7d826", -"generic": "bacfe825b19ef52bc792ab22", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "00714a615c9e7a090fb122c5", -"postgres": "bacfe825b19ef52bc792ab22", -"postgresql": "bacfe825b19ef52bc792ab22", +"generic": "00714a615c9e7a090fb122c5", +"motherduck": "9f4b51f000ffbe30f6b7d826", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "30d01707c0b130f64a4d8414", +"postgresql": "30d01707c0b130f64a4d8414", "snowflake": "bacfe825b19ef52bc792ab22", -"tsql": "bacfe825b19ef52bc792ab22" +"tsql": "00714a615c9e7a090fb122c5" } ], [ @@ -16277,8 +16277,8 @@ "databricks": "f85dfff687194a44f32eeb15", "duckdb": "f85dfff687194a44f32eeb15", "generic": "f85dfff687194a44f32eeb15", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f85dfff687194a44f32eeb15", +"motherduck": "f85dfff687194a44f32eeb15", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f85dfff687194a44f32eeb15", "postgresql": "f85dfff687194a44f32eeb15", "snowflake": "f85dfff687194a44f32eeb15", @@ -16294,8 +16294,8 @@ "databricks": "24b677b214ecdacdb007642e", "duckdb": "24b677b214ecdacdb007642e", "generic": "24b677b214ecdacdb007642e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "24b677b214ecdacdb007642e", +"motherduck": "24b677b214ecdacdb007642e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "24b677b214ecdacdb007642e", "postgresql": "24b677b214ecdacdb007642e", "snowflake": "24b677b214ecdacdb007642e", @@ -16311,8 +16311,8 @@ "databricks": "4c2128e6078118fd0d3856fa", "duckdb": "4c2128e6078118fd0d3856fa", "generic": "4c2128e6078118fd0d3856fa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4c2128e6078118fd0d3856fa", +"motherduck": "4c2128e6078118fd0d3856fa", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4c2128e6078118fd0d3856fa", "postgresql": "4c2128e6078118fd0d3856fa", "snowflake": "4c2128e6078118fd0d3856fa", @@ -16322,16 +16322,16 @@ [ "type", { -"DuckDB": "8698d724534891869075175e", -"None": "0017a88bb60ec3403c786ed8", +"DuckDB": "29525edc1ad146bad4372f59", +"None": "2795066c9a71fa3e3780c73b", "bigquery": "2795066c9a71fa3e3780c73b", -"databricks": "0017a88bb60ec3403c786ed8", -"duckdb": "8698d724534891869075175e", -"generic": "0017a88bb60ec3403c786ed8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "8698d724534891869075175e", -"postgresql": "8698d724534891869075175e", +"databricks": "2795066c9a71fa3e3780c73b", +"duckdb": "29525edc1ad146bad4372f59", +"generic": "2795066c9a71fa3e3780c73b", +"motherduck": "29525edc1ad146bad4372f59", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "29525edc1ad146bad4372f59", +"postgresql": "29525edc1ad146bad4372f59", "snowflake": "b3b752c645651b849e23d39a", "tsql": "d1dc9a140dbe2eb1874c6ad7" } @@ -16345,10 +16345,10 @@ "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -16362,8 +16362,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -16379,8 +16379,8 @@ "databricks": "e7b8de8bd235305d79e2b6b1", "duckdb": "e7b8de8bd235305d79e2b6b1", "generic": "e7b8de8bd235305d79e2b6b1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7b8de8bd235305d79e2b6b1", +"motherduck": "e7b8de8bd235305d79e2b6b1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7b8de8bd235305d79e2b6b1", "postgresql": "e7b8de8bd235305d79e2b6b1", "snowflake": "e7b8de8bd235305d79e2b6b1", @@ -16396,8 +16396,8 @@ "databricks": "9aff7fcc7d550e1a8d105471", "duckdb": "9aff7fcc7d550e1a8d105471", "generic": "9aff7fcc7d550e1a8d105471", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9aff7fcc7d550e1a8d105471", +"motherduck": "9aff7fcc7d550e1a8d105471", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9aff7fcc7d550e1a8d105471", "postgresql": "9aff7fcc7d550e1a8d105471", "snowflake": "9aff7fcc7d550e1a8d105471", @@ -16409,14 +16409,14 @@ { "DuckDB": "242bf50062e95bc8534ca887", "None": "242bf50062e95bc8534ca887", -"bigquery": "242bf50062e95bc8534ca887", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "242bf50062e95bc8534ca887", "duckdb": "242bf50062e95bc8534ca887", "generic": "242bf50062e95bc8534ca887", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "242bf50062e95bc8534ca887", -"postgres": "242bf50062e95bc8534ca887", -"postgresql": "242bf50062e95bc8534ca887", +"motherduck": "242bf50062e95bc8534ca887", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3f86c2b62e6298676c7001fd", +"postgresql": "3f86c2b62e6298676c7001fd", "snowflake": "242bf50062e95bc8534ca887", "tsql": "242bf50062e95bc8534ca887" } @@ -16430,10 +16430,10 @@ "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", -"postgres": "5d25d5e13f81700741695ed4", -"postgresql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62131fb5b065f5de2e74a0f1", +"postgresql": "62131fb5b065f5de2e74a0f1", "snowflake": "5d25d5e13f81700741695ed4", "tsql": "5d25d5e13f81700741695ed4" } @@ -16442,33 +16442,33 @@ "type", { "DuckDB": "13f1401e7cd410600a4c9485", -"None": "13f1401e7cd410600a4c9485", -"bigquery": "74a30f6aa9def0b58afc616c", -"databricks": "13f1401e7cd410600a4c9485", +"None": "75c45736873ac5c99a62bc20", +"bigquery": "a686bb6db17ab079cfc79224", +"databricks": "75c45736873ac5c99a62bc20", "duckdb": "13f1401e7cd410600a4c9485", -"generic": "13f1401e7cd410600a4c9485", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "13f1401e7cd410600a4c9485", +"generic": "75c45736873ac5c99a62bc20", +"motherduck": "13f1401e7cd410600a4c9485", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "13f1401e7cd410600a4c9485", "postgresql": "13f1401e7cd410600a4c9485", "snowflake": "9b5f44707b5f4764b82741be", -"tsql": "13f1401e7cd410600a4c9485" +"tsql": "75c45736873ac5c99a62bc20" } ], [ "type", { -"DuckDB": "6f851103102e7b986535376f", -"None": "6f851103102e7b986535376f", -"bigquery": "58b7e0908df666938398205f", -"databricks": "6f851103102e7b986535376f", -"duckdb": "6f851103102e7b986535376f", -"generic": "6f851103102e7b986535376f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1a853242fb56557e2865a86a", -"postgres": "6f851103102e7b986535376f", -"postgresql": "6f851103102e7b986535376f", -"snowflake": "87800133cac38d9119180c0a", +"DuckDB": "bf0a539a5f35525b6b4067c5", +"None": "8e2423c700938b33a6fea361", +"bigquery": "126d23e6f3408f484fe7bd92", +"databricks": "8e2423c700938b33a6fea361", +"duckdb": "bf0a539a5f35525b6b4067c5", +"generic": "8e2423c700938b33a6fea361", +"motherduck": "bf0a539a5f35525b6b4067c5", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "fac7e3f22dda1f3edd9dee28", +"postgresql": "fac7e3f22dda1f3edd9dee28", +"snowflake": "fe64d523696db51fa15d432e", "tsql": "0e9ef5373e5d52970e8e1656" } ], @@ -16481,8 +16481,8 @@ "databricks": "15be07c71f6871ae037caae3", "duckdb": "15be07c71f6871ae037caae3", "generic": "15be07c71f6871ae037caae3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "15be07c71f6871ae037caae3", +"motherduck": "15be07c71f6871ae037caae3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "15be07c71f6871ae037caae3", "postgresql": "15be07c71f6871ae037caae3", "snowflake": "15be07c71f6871ae037caae3", @@ -16498,8 +16498,8 @@ "databricks": "b210f8eb26c37b422f786c6b", "duckdb": "b210f8eb26c37b422f786c6b", "generic": "b210f8eb26c37b422f786c6b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b210f8eb26c37b422f786c6b", +"motherduck": "b210f8eb26c37b422f786c6b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b210f8eb26c37b422f786c6b", "postgresql": "b210f8eb26c37b422f786c6b", "snowflake": "b210f8eb26c37b422f786c6b", @@ -16515,8 +16515,8 @@ "databricks": "3d23f811a03db519b598c815", "duckdb": "3d23f811a03db519b598c815", "generic": "3d23f811a03db519b598c815", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3d23f811a03db519b598c815", +"motherduck": "3d23f811a03db519b598c815", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3d23f811a03db519b598c815", "postgresql": "3d23f811a03db519b598c815", "snowflake": "3d23f811a03db519b598c815", @@ -16532,8 +16532,8 @@ "databricks": "fa55698f69d154f89214f351", "duckdb": "fa55698f69d154f89214f351", "generic": "fa55698f69d154f89214f351", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fa55698f69d154f89214f351", +"motherduck": "fa55698f69d154f89214f351", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fa55698f69d154f89214f351", "postgresql": "fa55698f69d154f89214f351", "snowflake": "fa55698f69d154f89214f351", @@ -16549,8 +16549,8 @@ "databricks": "3009be83c84cdfc92bbb075e", "duckdb": "3009be83c84cdfc92bbb075e", "generic": "3009be83c84cdfc92bbb075e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3009be83c84cdfc92bbb075e", +"motherduck": "3009be83c84cdfc92bbb075e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3009be83c84cdfc92bbb075e", "postgresql": "3009be83c84cdfc92bbb075e", "snowflake": "3009be83c84cdfc92bbb075e", @@ -16566,10 +16566,10 @@ "databricks": "5d25d5e13f81700741695ed4", "duckdb": "5d25d5e13f81700741695ed4", "generic": "5d25d5e13f81700741695ed4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5d25d5e13f81700741695ed4", -"postgres": "5d25d5e13f81700741695ed4", -"postgresql": "5d25d5e13f81700741695ed4", +"motherduck": "5d25d5e13f81700741695ed4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62131fb5b065f5de2e74a0f1", +"postgresql": "62131fb5b065f5de2e74a0f1", "snowflake": "5d25d5e13f81700741695ed4", "tsql": "5d25d5e13f81700741695ed4" } @@ -16583,8 +16583,8 @@ "databricks": "9d6807d797be08f8376a5a19", "duckdb": "9d6807d797be08f8376a5a19", "generic": "9d6807d797be08f8376a5a19", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9d6807d797be08f8376a5a19", +"motherduck": "9d6807d797be08f8376a5a19", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9d6807d797be08f8376a5a19", "postgresql": "9d6807d797be08f8376a5a19", "snowflake": "9d6807d797be08f8376a5a19", @@ -16600,8 +16600,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -16617,8 +16617,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -16630,14 +16630,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -16647,12 +16647,12 @@ { "DuckDB": "100c78a43fb3cbda50787a10", "None": "100c78a43fb3cbda50787a10", -"bigquery": "100c78a43fb3cbda50787a10", +"bigquery": "8d325bf2397f80882c281b76", "databricks": "100c78a43fb3cbda50787a10", "duckdb": "100c78a43fb3cbda50787a10", "generic": "100c78a43fb3cbda50787a10", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "100c78a43fb3cbda50787a10", +"motherduck": "100c78a43fb3cbda50787a10", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "100c78a43fb3cbda50787a10", "postgresql": "100c78a43fb3cbda50787a10", "snowflake": "100c78a43fb3cbda50787a10", @@ -16664,14 +16664,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -16681,14 +16681,14 @@ { "DuckDB": "624eb95d280fa0653ec73f7f", "None": "624eb95d280fa0653ec73f7f", -"bigquery": "624eb95d280fa0653ec73f7f", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "624eb95d280fa0653ec73f7f", "duckdb": "624eb95d280fa0653ec73f7f", "generic": "624eb95d280fa0653ec73f7f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "624eb95d280fa0653ec73f7f", -"postgres": "624eb95d280fa0653ec73f7f", -"postgresql": "624eb95d280fa0653ec73f7f", +"motherduck": "624eb95d280fa0653ec73f7f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9dd376ce83bd3444e52fdb88", +"postgresql": "9dd376ce83bd3444e52fdb88", "snowflake": "624eb95d280fa0653ec73f7f", "tsql": "624eb95d280fa0653ec73f7f" } @@ -16698,14 +16698,14 @@ { "DuckDB": "6b04f574865e71733e09ba1d", "None": "6b04f574865e71733e09ba1d", -"bigquery": "6b04f574865e71733e09ba1d", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "6b04f574865e71733e09ba1d", "duckdb": "6b04f574865e71733e09ba1d", "generic": "6b04f574865e71733e09ba1d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6b04f574865e71733e09ba1d", -"postgres": "6b04f574865e71733e09ba1d", -"postgresql": "6b04f574865e71733e09ba1d", +"motherduck": "6b04f574865e71733e09ba1d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e4d1b966e983269b60041cc7", +"postgresql": "e4d1b966e983269b60041cc7", "snowflake": "6b04f574865e71733e09ba1d", "tsql": "6b04f574865e71733e09ba1d" } @@ -16719,8 +16719,8 @@ "databricks": "8a3f094f18fb4047f0febf59", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "8a3f094f18fb4047f0febf59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a3f094f18fb4047f0febf59", "postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "850deea990b52b82b36a9848", @@ -16736,8 +16736,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -16753,8 +16753,8 @@ "databricks": "07f8200cee37aa0f6990da3d", "duckdb": "07f8200cee37aa0f6990da3d", "generic": "07f8200cee37aa0f6990da3d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "07f8200cee37aa0f6990da3d", +"motherduck": "07f8200cee37aa0f6990da3d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "07f8200cee37aa0f6990da3d", "postgresql": "07f8200cee37aa0f6990da3d", "snowflake": "07f8200cee37aa0f6990da3d", @@ -16770,8 +16770,8 @@ "databricks": "ab5ad6180652b3b00d1f5b01", "duckdb": "ab5ad6180652b3b00d1f5b01", "generic": "ab5ad6180652b3b00d1f5b01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab5ad6180652b3b00d1f5b01", +"motherduck": "ab5ad6180652b3b00d1f5b01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab5ad6180652b3b00d1f5b01", "postgresql": "ab5ad6180652b3b00d1f5b01", "snowflake": "f43dac38f34a1c0618a3f812", @@ -16787,8 +16787,8 @@ "databricks": "47a5c4cbcdc5fda84d74a947", "duckdb": "47a5c4cbcdc5fda84d74a947", "generic": "47a5c4cbcdc5fda84d74a947", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "47a5c4cbcdc5fda84d74a947", +"motherduck": "47a5c4cbcdc5fda84d74a947", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "47a5c4cbcdc5fda84d74a947", "postgresql": "47a5c4cbcdc5fda84d74a947", "snowflake": "47a5c4cbcdc5fda84d74a947", @@ -16804,8 +16804,8 @@ "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e46d4be44ab9e500e9190d26", "postgresql": "e46d4be44ab9e500e9190d26", "snowflake": "e46d4be44ab9e500e9190d26", @@ -16821,10 +16821,10 @@ "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b0ce99394388f977f67fd1df", +"postgresql": "b0ce99394388f977f67fd1df", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -16834,14 +16834,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -16855,8 +16855,8 @@ "databricks": "4fb572024031cb7631777be1", "duckdb": "792f7d3389f076f50c713946", "generic": "792f7d3389f076f50c713946", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "792f7d3389f076f50c713946", +"motherduck": "792f7d3389f076f50c713946", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "792f7d3389f076f50c713946", "postgresql": "792f7d3389f076f50c713946", "snowflake": "792f7d3389f076f50c713946", @@ -16868,12 +16868,12 @@ { "DuckDB": "aae2b1fe6f2d9807ecde412b", "None": "aae2b1fe6f2d9807ecde412b", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "aae2b1fe6f2d9807ecde412b", "duckdb": "aae2b1fe6f2d9807ecde412b", "generic": "aae2b1fe6f2d9807ecde412b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aae2b1fe6f2d9807ecde412b", +"motherduck": "aae2b1fe6f2d9807ecde412b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aae2b1fe6f2d9807ecde412b", "postgresql": "aae2b1fe6f2d9807ecde412b", "snowflake": "aae2b1fe6f2d9807ecde412b", @@ -16884,17 +16884,17 @@ "type", { "DuckDB": "16adcdd909f51e3f33d7cdde", -"None": "1f3277ab2fff19e327e21fe0", -"bigquery": "1f3277ab2fff19e327e21fe0", -"databricks": "1f3277ab2fff19e327e21fe0", +"None": "ac92c18bc03647c2ff622e35", +"bigquery": "ac92c18bc03647c2ff622e35", +"databricks": "ac92c18bc03647c2ff622e35", "duckdb": "16adcdd909f51e3f33d7cdde", -"generic": "1f3277ab2fff19e327e21fe0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "de3f085b785f38a4bda19034", -"postgresql": "de3f085b785f38a4bda19034", +"generic": "ac92c18bc03647c2ff622e35", +"motherduck": "16adcdd909f51e3f33d7cdde", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ac92c18bc03647c2ff622e35", +"postgresql": "ac92c18bc03647c2ff622e35", "snowflake": "42ed29994060215ec30494f1", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"tsql": "ac92c18bc03647c2ff622e35" } ], [ @@ -16906,8 +16906,8 @@ "databricks": "690b44350cdb6852c9bc71a3", "duckdb": "690b44350cdb6852c9bc71a3", "generic": "690b44350cdb6852c9bc71a3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "690b44350cdb6852c9bc71a3", +"motherduck": "690b44350cdb6852c9bc71a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "690b44350cdb6852c9bc71a3", "postgresql": "690b44350cdb6852c9bc71a3", "snowflake": "690b44350cdb6852c9bc71a3", @@ -16919,14 +16919,14 @@ { "DuckDB": "3a7ffeb8f81df6ccd57f3689", "None": "3a7ffeb8f81df6ccd57f3689", -"bigquery": "3a7ffeb8f81df6ccd57f3689", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "3a7ffeb8f81df6ccd57f3689", "duckdb": "3a7ffeb8f81df6ccd57f3689", "generic": "3a7ffeb8f81df6ccd57f3689", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3a7ffeb8f81df6ccd57f3689", -"postgres": "3a7ffeb8f81df6ccd57f3689", -"postgresql": "3a7ffeb8f81df6ccd57f3689", +"motherduck": "3a7ffeb8f81df6ccd57f3689", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "062e51ec86ccd542eb78500d", +"postgresql": "062e51ec86ccd542eb78500d", "snowflake": "3a7ffeb8f81df6ccd57f3689", "tsql": "3a7ffeb8f81df6ccd57f3689" } @@ -16940,8 +16940,8 @@ "databricks": "c752fd9fe2ad3b86b8b899da", "duckdb": "c752fd9fe2ad3b86b8b899da", "generic": "c752fd9fe2ad3b86b8b899da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c752fd9fe2ad3b86b8b899da", +"motherduck": "c752fd9fe2ad3b86b8b899da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -16957,8 +16957,8 @@ "databricks": "01d51e9454e607e1bab153ef", "duckdb": "01d51e9454e607e1bab153ef", "generic": "01d51e9454e607e1bab153ef", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "01d51e9454e607e1bab153ef", +"motherduck": "01d51e9454e607e1bab153ef", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "01d51e9454e607e1bab153ef", "postgresql": "01d51e9454e607e1bab153ef", "snowflake": "01d51e9454e607e1bab153ef", @@ -16974,8 +16974,8 @@ "databricks": "802f1c1fcb97ddd5679172d2", "duckdb": "802f1c1fcb97ddd5679172d2", "generic": "802f1c1fcb97ddd5679172d2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "802f1c1fcb97ddd5679172d2", +"motherduck": "802f1c1fcb97ddd5679172d2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "802f1c1fcb97ddd5679172d2", "postgresql": "802f1c1fcb97ddd5679172d2", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -16991,10 +16991,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -17008,8 +17008,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -17025,8 +17025,8 @@ "databricks": "fe0bedb7dda0e9247a51fdee", "duckdb": "fe0bedb7dda0e9247a51fdee", "generic": "fe0bedb7dda0e9247a51fdee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe0bedb7dda0e9247a51fdee", +"motherduck": "fe0bedb7dda0e9247a51fdee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fe0bedb7dda0e9247a51fdee", "postgresql": "fe0bedb7dda0e9247a51fdee", "snowflake": "fe0bedb7dda0e9247a51fdee", @@ -17042,8 +17042,8 @@ "databricks": "e1250582e271934cbcb2a788", "duckdb": "e1250582e271934cbcb2a788", "generic": "e1250582e271934cbcb2a788", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e1250582e271934cbcb2a788", +"motherduck": "e1250582e271934cbcb2a788", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e1250582e271934cbcb2a788", "postgresql": "e1250582e271934cbcb2a788", "snowflake": "e1250582e271934cbcb2a788", @@ -17055,14 +17055,14 @@ { "DuckDB": "dbf9ebbc7016f5ac5df2817e", "None": "dbf9ebbc7016f5ac5df2817e", -"bigquery": "dbf9ebbc7016f5ac5df2817e", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "dbf9ebbc7016f5ac5df2817e", "duckdb": "dbf9ebbc7016f5ac5df2817e", "generic": "dbf9ebbc7016f5ac5df2817e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dbf9ebbc7016f5ac5df2817e", -"postgres": "dbf9ebbc7016f5ac5df2817e", -"postgresql": "dbf9ebbc7016f5ac5df2817e", +"motherduck": "dbf9ebbc7016f5ac5df2817e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "4cccf86e62d627a7a8b57726", +"postgresql": "4cccf86e62d627a7a8b57726", "snowflake": "dbf9ebbc7016f5ac5df2817e", "tsql": "dbf9ebbc7016f5ac5df2817e" } @@ -17072,14 +17072,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -17093,8 +17093,8 @@ "databricks": "1bdf5908ca249b2331a0427c", "duckdb": "1bdf5908ca249b2331a0427c", "generic": "1bdf5908ca249b2331a0427c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1bdf5908ca249b2331a0427c", +"motherduck": "1bdf5908ca249b2331a0427c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1bdf5908ca249b2331a0427c", "postgresql": "1bdf5908ca249b2331a0427c", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -17110,8 +17110,8 @@ "databricks": "6bd27be6a47ef34ec1defbad", "duckdb": "b0791f9f270e764eeeb6fb8e", "generic": "c7c38fdeaeb7858a44976f1d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c7c38fdeaeb7858a44976f1d", +"motherduck": "b0791f9f270e764eeeb6fb8e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4d7604d41e3585ec187dec82", "postgresql": "4d7604d41e3585ec187dec82", "snowflake": "c7c38fdeaeb7858a44976f1d", @@ -17127,8 +17127,8 @@ "databricks": "0b9d9879e289ea9a6a3e3e70", "duckdb": "0b9d9879e289ea9a6a3e3e70", "generic": "0b9d9879e289ea9a6a3e3e70", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0b9d9879e289ea9a6a3e3e70", +"motherduck": "0b9d9879e289ea9a6a3e3e70", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0b9d9879e289ea9a6a3e3e70", "postgresql": "0b9d9879e289ea9a6a3e3e70", "snowflake": "0b9d9879e289ea9a6a3e3e70", @@ -17144,8 +17144,8 @@ "databricks": "ba0da7ef018b990fcc83cba1", "duckdb": "ba0da7ef018b990fcc83cba1", "generic": "ba0da7ef018b990fcc83cba1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ba0da7ef018b990fcc83cba1", +"motherduck": "ba0da7ef018b990fcc83cba1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ba0da7ef018b990fcc83cba1", "postgresql": "ba0da7ef018b990fcc83cba1", "snowflake": "ba0da7ef018b990fcc83cba1", @@ -17155,33 +17155,33 @@ [ "type", { -"DuckDB": "51b0e597909858f7f7aa4571", -"None": "51b0e597909858f7f7aa4571", +"DuckDB": "9efea5795b256d49cab6a366", +"None": "9efea5795b256d49cab6a366", "bigquery": "deb9d53b04b7825e460d826a", -"databricks": "846f4ee64c34aab0cdabb9a0", -"duckdb": "51b0e597909858f7f7aa4571", -"generic": "51b0e597909858f7f7aa4571", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "51b0e597909858f7f7aa4571", -"postgres": "51b0e597909858f7f7aa4571", -"postgresql": "51b0e597909858f7f7aa4571", +"databricks": "d0f04a0ad4aff66d31330f7a", +"duckdb": "9efea5795b256d49cab6a366", +"generic": "9efea5795b256d49cab6a366", +"motherduck": "9efea5795b256d49cab6a366", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9efea5795b256d49cab6a366", +"postgresql": "9efea5795b256d49cab6a366", "snowflake": "50d14e3a5691e6cb8689aa29", -"tsql": "51b0e597909858f7f7aa4571" +"tsql": "9efea5795b256d49cab6a366" } ], [ "type", { -"DuckDB": "8698d724534891869075175e", -"None": "0017a88bb60ec3403c786ed8", +"DuckDB": "29525edc1ad146bad4372f59", +"None": "2795066c9a71fa3e3780c73b", "bigquery": "2795066c9a71fa3e3780c73b", -"databricks": "0017a88bb60ec3403c786ed8", -"duckdb": "8698d724534891869075175e", -"generic": "0017a88bb60ec3403c786ed8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "8698d724534891869075175e", -"postgresql": "8698d724534891869075175e", +"databricks": "2795066c9a71fa3e3780c73b", +"duckdb": "29525edc1ad146bad4372f59", +"generic": "2795066c9a71fa3e3780c73b", +"motherduck": "29525edc1ad146bad4372f59", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "29525edc1ad146bad4372f59", +"postgresql": "29525edc1ad146bad4372f59", "snowflake": "b3b752c645651b849e23d39a", "tsql": "d1dc9a140dbe2eb1874c6ad7" } @@ -17191,14 +17191,14 @@ { "DuckDB": "881106a4a0d2de0ff7c56373", "None": "881106a4a0d2de0ff7c56373", -"bigquery": "881106a4a0d2de0ff7c56373", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "881106a4a0d2de0ff7c56373", "duckdb": "881106a4a0d2de0ff7c56373", "generic": "881106a4a0d2de0ff7c56373", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "881106a4a0d2de0ff7c56373", -"postgres": "881106a4a0d2de0ff7c56373", -"postgresql": "881106a4a0d2de0ff7c56373", +"motherduck": "881106a4a0d2de0ff7c56373", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cf167ec395fc843602e3b1e0", +"postgresql": "cf167ec395fc843602e3b1e0", "snowflake": "881106a4a0d2de0ff7c56373", "tsql": "881106a4a0d2de0ff7c56373" } @@ -17212,10 +17212,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -17229,10 +17229,10 @@ "databricks": "013a8446f043affffcf194f8", "duckdb": "013a8446f043affffcf194f8", "generic": "013a8446f043affffcf194f8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "013a8446f043affffcf194f8", -"postgres": "013a8446f043affffcf194f8", -"postgresql": "013a8446f043affffcf194f8", +"motherduck": "013a8446f043affffcf194f8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b0477b36767a62145aa9158c", +"postgresql": "b0477b36767a62145aa9158c", "snowflake": "97a20344d811fb77c7690863", "tsql": "013a8446f043affffcf194f8" } @@ -17246,10 +17246,10 @@ "databricks": "804fec6876d0b43935790142", "duckdb": "804fec6876d0b43935790142", "generic": "804fec6876d0b43935790142", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "804fec6876d0b43935790142", -"postgres": "804fec6876d0b43935790142", -"postgresql": "804fec6876d0b43935790142", +"motherduck": "804fec6876d0b43935790142", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "191fc59c754706512337edc1", +"postgresql": "191fc59c754706512337edc1", "snowflake": "804fec6876d0b43935790142", "tsql": "804fec6876d0b43935790142" } @@ -17263,8 +17263,8 @@ "databricks": "0dcd6a19b7dc6f6b6d4a5006", "duckdb": "0dcd6a19b7dc6f6b6d4a5006", "generic": "0dcd6a19b7dc6f6b6d4a5006", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0dcd6a19b7dc6f6b6d4a5006", +"motherduck": "0dcd6a19b7dc6f6b6d4a5006", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0dcd6a19b7dc6f6b6d4a5006", "postgresql": "0dcd6a19b7dc6f6b6d4a5006", "snowflake": "0dcd6a19b7dc6f6b6d4a5006", @@ -17280,8 +17280,8 @@ "databricks": "9d6807d797be08f8376a5a19", "duckdb": "9d6807d797be08f8376a5a19", "generic": "9d6807d797be08f8376a5a19", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9d6807d797be08f8376a5a19", +"motherduck": "9d6807d797be08f8376a5a19", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9d6807d797be08f8376a5a19", "postgresql": "9d6807d797be08f8376a5a19", "snowflake": "9d6807d797be08f8376a5a19", @@ -17297,8 +17297,8 @@ "databricks": "3915f3f73328f0838497b2d5", "duckdb": "3915f3f73328f0838497b2d5", "generic": "3915f3f73328f0838497b2d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3915f3f73328f0838497b2d5", +"motherduck": "3915f3f73328f0838497b2d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3915f3f73328f0838497b2d5", "postgresql": "3915f3f73328f0838497b2d5", "snowflake": "f43dac38f34a1c0618a3f812", @@ -17314,8 +17314,8 @@ "databricks": "0dcd6a19b7dc6f6b6d4a5006", "duckdb": "0dcd6a19b7dc6f6b6d4a5006", "generic": "0dcd6a19b7dc6f6b6d4a5006", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0dcd6a19b7dc6f6b6d4a5006", +"motherduck": "0dcd6a19b7dc6f6b6d4a5006", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0dcd6a19b7dc6f6b6d4a5006", "postgresql": "0dcd6a19b7dc6f6b6d4a5006", "snowflake": "0dcd6a19b7dc6f6b6d4a5006", @@ -17327,14 +17327,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -17348,8 +17348,8 @@ "databricks": "d03c5e5797a8cbc0c6a81112", "duckdb": "d03c5e5797a8cbc0c6a81112", "generic": "d03c5e5797a8cbc0c6a81112", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d03c5e5797a8cbc0c6a81112", +"motherduck": "d03c5e5797a8cbc0c6a81112", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d03c5e5797a8cbc0c6a81112", "postgresql": "d03c5e5797a8cbc0c6a81112", "snowflake": "d03c5e5797a8cbc0c6a81112", @@ -17365,8 +17365,8 @@ "databricks": "6cffd1617edd6454241ee732", "duckdb": "6cffd1617edd6454241ee732", "generic": "6cffd1617edd6454241ee732", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6cffd1617edd6454241ee732", +"motherduck": "6cffd1617edd6454241ee732", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6cffd1617edd6454241ee732", "postgresql": "6cffd1617edd6454241ee732", "snowflake": "6cffd1617edd6454241ee732", @@ -17382,8 +17382,8 @@ "databricks": "98d56a64e9a7ee905a58c95c", "duckdb": "815698ef0c2515c76f250086", "generic": "98d56a64e9a7ee905a58c95c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "815698ef0c2515c76f250086", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "815698ef0c2515c76f250086", "postgresql": "815698ef0c2515c76f250086", "snowflake": "822641f83fbdcddae47b409f", @@ -17399,8 +17399,8 @@ "databricks": "1275b5b0c52f69cf592c004b", "duckdb": "1275b5b0c52f69cf592c004b", "generic": "1275b5b0c52f69cf592c004b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1275b5b0c52f69cf592c004b", +"motherduck": "1275b5b0c52f69cf592c004b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1275b5b0c52f69cf592c004b", "postgresql": "1275b5b0c52f69cf592c004b", "snowflake": "1275b5b0c52f69cf592c004b", @@ -17416,8 +17416,8 @@ "databricks": "80d6c9c5f502ef25b9434309", "duckdb": "80d6c9c5f502ef25b9434309", "generic": "80d6c9c5f502ef25b9434309", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "80d6c9c5f502ef25b9434309", +"motherduck": "80d6c9c5f502ef25b9434309", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "80d6c9c5f502ef25b9434309", "postgresql": "80d6c9c5f502ef25b9434309", "snowflake": "80d6c9c5f502ef25b9434309", @@ -17433,8 +17433,8 @@ "databricks": "42f6a9fe8a8cc486c2c77ae1", "duckdb": "42f6a9fe8a8cc486c2c77ae1", "generic": "42f6a9fe8a8cc486c2c77ae1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "42f6a9fe8a8cc486c2c77ae1", +"motherduck": "42f6a9fe8a8cc486c2c77ae1", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "42f6a9fe8a8cc486c2c77ae1", "postgresql": "42f6a9fe8a8cc486c2c77ae1", "snowflake": "42f6a9fe8a8cc486c2c77ae1", @@ -17450,8 +17450,8 @@ "databricks": "7894730ae1c06f4ea3920947", "duckdb": "7894730ae1c06f4ea3920947", "generic": "7894730ae1c06f4ea3920947", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7894730ae1c06f4ea3920947", +"motherduck": "7894730ae1c06f4ea3920947", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7894730ae1c06f4ea3920947", "postgresql": "7894730ae1c06f4ea3920947", "snowflake": "7894730ae1c06f4ea3920947", @@ -17461,18 +17461,18 @@ [ "type", { -"DuckDB": "f0c468b3e6d6df98b4358aff", -"None": "752ae7fc618de4a182e18bd3", -"bigquery": "752ae7fc618de4a182e18bd3", -"databricks": "aa5bc52ba6ea9e0a428084da", -"duckdb": "f0c468b3e6d6df98b4358aff", -"generic": "752ae7fc618de4a182e18bd3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "f0c468b3e6d6df98b4358aff", -"postgresql": "f0c468b3e6d6df98b4358aff", +"DuckDB": "a8f9cb9212d342eb9d2854d0", +"None": "3213ca7d92e0eff2f419451a", +"bigquery": "3213ca7d92e0eff2f419451a", +"databricks": "3213ca7d92e0eff2f419451a", +"duckdb": "a8f9cb9212d342eb9d2854d0", +"generic": "3213ca7d92e0eff2f419451a", +"motherduck": "a8f9cb9212d342eb9d2854d0", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3213ca7d92e0eff2f419451a", +"postgresql": "3213ca7d92e0eff2f419451a", "snowflake": "6dc518f5329c32ce13bfa506", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"tsql": "3213ca7d92e0eff2f419451a" } ], [ @@ -17480,14 +17480,14 @@ { "DuckDB": "9829fa9011a92bb968bb5996", "None": "9829fa9011a92bb968bb5996", -"bigquery": "9829fa9011a92bb968bb5996", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "9829fa9011a92bb968bb5996", "duckdb": "9829fa9011a92bb968bb5996", "generic": "9829fa9011a92bb968bb5996", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9829fa9011a92bb968bb5996", -"postgres": "9829fa9011a92bb968bb5996", -"postgresql": "9829fa9011a92bb968bb5996", +"motherduck": "9829fa9011a92bb968bb5996", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "4e937b76a5c3e503c1fd6e22", +"postgresql": "4e937b76a5c3e503c1fd6e22", "snowflake": "9829fa9011a92bb968bb5996", "tsql": "9829fa9011a92bb968bb5996" } @@ -17501,8 +17501,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -17518,8 +17518,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -17531,14 +17531,14 @@ { "DuckDB": "18db5afe2c1e80d1ee652b54", "None": "18db5afe2c1e80d1ee652b54", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", -"postgres": "18db5afe2c1e80d1ee652b54", -"postgresql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "47d64a5994d42a2b577166ea", +"postgresql": "47d64a5994d42a2b577166ea", "snowflake": "18db5afe2c1e80d1ee652b54", "tsql": "18db5afe2c1e80d1ee652b54" } @@ -17547,17 +17547,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -17569,10 +17569,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -17586,8 +17586,8 @@ "databricks": "8993aba016a0adde404608e3", "duckdb": "8993aba016a0adde404608e3", "generic": "8993aba016a0adde404608e3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8993aba016a0adde404608e3", +"motherduck": "8993aba016a0adde404608e3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8993aba016a0adde404608e3", "postgresql": "8993aba016a0adde404608e3", "snowflake": "8993aba016a0adde404608e3", @@ -17599,14 +17599,14 @@ { "DuckDB": "837168a49b84e7ed918ea148", "None": "837168a49b84e7ed918ea148", -"bigquery": "837168a49b84e7ed918ea148", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "837168a49b84e7ed918ea148", "duckdb": "837168a49b84e7ed918ea148", "generic": "837168a49b84e7ed918ea148", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "837168a49b84e7ed918ea148", -"postgres": "837168a49b84e7ed918ea148", -"postgresql": "837168a49b84e7ed918ea148", +"motherduck": "837168a49b84e7ed918ea148", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c28e9468b9f7a7f4e67d5b30", +"postgresql": "c28e9468b9f7a7f4e67d5b30", "snowflake": "837168a49b84e7ed918ea148", "tsql": "837168a49b84e7ed918ea148" } @@ -17620,8 +17620,8 @@ "databricks": "3915f3f73328f0838497b2d5", "duckdb": "3915f3f73328f0838497b2d5", "generic": "3915f3f73328f0838497b2d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3915f3f73328f0838497b2d5", +"motherduck": "3915f3f73328f0838497b2d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3915f3f73328f0838497b2d5", "postgresql": "3915f3f73328f0838497b2d5", "snowflake": "f43dac38f34a1c0618a3f812", @@ -17637,8 +17637,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -17654,8 +17654,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -17667,14 +17667,14 @@ { "DuckDB": "08736485547ba339930697e9", "None": "08736485547ba339930697e9", -"bigquery": "08736485547ba339930697e9", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "08736485547ba339930697e9", "duckdb": "08736485547ba339930697e9", "generic": "08736485547ba339930697e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "08736485547ba339930697e9", -"postgres": "08736485547ba339930697e9", -"postgresql": "08736485547ba339930697e9", +"motherduck": "08736485547ba339930697e9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eecd452b4f5f3f4133d0bd0a", +"postgresql": "eecd452b4f5f3f4133d0bd0a", "snowflake": "08736485547ba339930697e9", "tsql": "08736485547ba339930697e9" } @@ -17688,8 +17688,8 @@ "databricks": "c6889f2965db73017be6f6e7", "duckdb": "c6889f2965db73017be6f6e7", "generic": "c6889f2965db73017be6f6e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6889f2965db73017be6f6e7", +"motherduck": "c6889f2965db73017be6f6e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6889f2965db73017be6f6e7", "postgresql": "c6889f2965db73017be6f6e7", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -17701,12 +17701,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -17718,14 +17718,14 @@ { "DuckDB": "624eb95d280fa0653ec73f7f", "None": "624eb95d280fa0653ec73f7f", -"bigquery": "624eb95d280fa0653ec73f7f", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "624eb95d280fa0653ec73f7f", "duckdb": "624eb95d280fa0653ec73f7f", "generic": "624eb95d280fa0653ec73f7f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "624eb95d280fa0653ec73f7f", -"postgres": "624eb95d280fa0653ec73f7f", -"postgresql": "624eb95d280fa0653ec73f7f", +"motherduck": "624eb95d280fa0653ec73f7f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9dd376ce83bd3444e52fdb88", +"postgresql": "9dd376ce83bd3444e52fdb88", "snowflake": "624eb95d280fa0653ec73f7f", "tsql": "624eb95d280fa0653ec73f7f" } @@ -17734,17 +17734,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -17756,10 +17756,10 @@ "databricks": "bce60580ef3a1dc4ce569fff", "duckdb": "bce60580ef3a1dc4ce569fff", "generic": "bce60580ef3a1dc4ce569fff", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bce60580ef3a1dc4ce569fff", -"postgres": "bce60580ef3a1dc4ce569fff", -"postgresql": "bce60580ef3a1dc4ce569fff", +"motherduck": "bce60580ef3a1dc4ce569fff", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eee236ea1427bdddbaf088ac", +"postgresql": "eee236ea1427bdddbaf088ac", "snowflake": "bce60580ef3a1dc4ce569fff", "tsql": "bce60580ef3a1dc4ce569fff" } @@ -17773,8 +17773,8 @@ "databricks": "a104f9b34b3d9d798095a6b4", "duckdb": "a104f9b34b3d9d798095a6b4", "generic": "a104f9b34b3d9d798095a6b4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a104f9b34b3d9d798095a6b4", +"motherduck": "a104f9b34b3d9d798095a6b4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a104f9b34b3d9d798095a6b4", "postgresql": "a104f9b34b3d9d798095a6b4", "snowflake": "a104f9b34b3d9d798095a6b4", @@ -17785,17 +17785,17 @@ "type", { "DuckDB": "858855737713d0cff353f8ac", -"None": "858855737713d0cff353f8ac", -"bigquery": "858855737713d0cff353f8ac", -"databricks": "858855737713d0cff353f8ac", +"None": "50b29536a9929d11dbcb949a", +"bigquery": "8b8d5f75ba6dec68b4cebc6b", +"databricks": "50b29536a9929d11dbcb949a", "duckdb": "858855737713d0cff353f8ac", -"generic": "858855737713d0cff353f8ac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "858855737713d0cff353f8ac", -"postgres": "858855737713d0cff353f8ac", -"postgresql": "858855737713d0cff353f8ac", +"generic": "50b29536a9929d11dbcb949a", +"motherduck": "858855737713d0cff353f8ac", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "74a30f6aa9def0b58afc616c", +"postgresql": "74a30f6aa9def0b58afc616c", "snowflake": "858855737713d0cff353f8ac", -"tsql": "858855737713d0cff353f8ac" +"tsql": "50b29536a9929d11dbcb949a" } ], [ @@ -17803,12 +17803,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -17824,8 +17824,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -17841,8 +17841,8 @@ "databricks": "4ce8ae479c93f9ff48189fc3", "duckdb": "4ce8ae479c93f9ff48189fc3", "generic": "4ce8ae479c93f9ff48189fc3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4ce8ae479c93f9ff48189fc3", +"motherduck": "4ce8ae479c93f9ff48189fc3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4ce8ae479c93f9ff48189fc3", "postgresql": "4ce8ae479c93f9ff48189fc3", "snowflake": "4ce8ae479c93f9ff48189fc3", @@ -17858,8 +17858,8 @@ "databricks": "fb5907c56dd752ac5f1dbd95", "duckdb": "fb5907c56dd752ac5f1dbd95", "generic": "fb5907c56dd752ac5f1dbd95", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fb5907c56dd752ac5f1dbd95", +"motherduck": "fb5907c56dd752ac5f1dbd95", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fb5907c56dd752ac5f1dbd95", "postgresql": "fb5907c56dd752ac5f1dbd95", "snowflake": "fb5907c56dd752ac5f1dbd95", @@ -17875,8 +17875,8 @@ "databricks": "aab5b077409069695e7d847d", "duckdb": "aab5b077409069695e7d847d", "generic": "aab5b077409069695e7d847d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab5b077409069695e7d847d", +"motherduck": "aab5b077409069695e7d847d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aab5b077409069695e7d847d", "postgresql": "aab5b077409069695e7d847d", "snowflake": "aab5b077409069695e7d847d", @@ -17887,17 +17887,17 @@ "type", { "DuckDB": "d81d8c19074af0e39239164d", -"None": "1fec266134381c760a254d16", -"bigquery": "1fec266134381c760a254d16", -"databricks": "1fec266134381c760a254d16", +"None": "a52676af0c6b750acc33f021", +"bigquery": "1e75e5227bea0ae294ac7af1", +"databricks": "a52676af0c6b750acc33f021", "duckdb": "d81d8c19074af0e39239164d", -"generic": "1fec266134381c760a254d16", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1fec266134381c760a254d16", -"postgres": "1fec266134381c760a254d16", -"postgresql": "1fec266134381c760a254d16", +"generic": "a52676af0c6b750acc33f021", +"motherduck": "d81d8c19074af0e39239164d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a52676af0c6b750acc33f021", +"postgresql": "a52676af0c6b750acc33f021", "snowflake": "1fec266134381c760a254d16", -"tsql": "1fec266134381c760a254d16" +"tsql": "a52676af0c6b750acc33f021" } ], [ @@ -17909,8 +17909,8 @@ "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "de89632e277dbc8c97115419", "postgresql": "de89632e277dbc8c97115419", "snowflake": "4b356542044f1267fcfc487a", @@ -17926,10 +17926,10 @@ "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -17943,8 +17943,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "111af35848fe500ec2ae54b2", "generic": "111af35848fe500ec2ae54b2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e3bc1b8bad22d8104417b00f", +"motherduck": "111af35848fe500ec2ae54b2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "111af35848fe500ec2ae54b2", "postgresql": "111af35848fe500ec2ae54b2", "snowflake": "e3bc1b8bad22d8104417b00f", @@ -17955,17 +17955,17 @@ "type", { "DuckDB": "a428302e4848ffe877284c84", -"None": "a428302e4848ffe877284c84", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "a428302e4848ffe877284c84", +"None": "c9b810639795a7bfab91b98d", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "c9b810639795a7bfab91b98d", "duckdb": "a428302e4848ffe877284c84", -"generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"generic": "c9b810639795a7bfab91b98d", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "a428302e4848ffe877284c84" +"tsql": "c9b810639795a7bfab91b98d" } ], [ @@ -17973,12 +17973,12 @@ { "DuckDB": "47d74d1b45fedd02ec465220", "None": "47d74d1b45fedd02ec465220", -"bigquery": "47d74d1b45fedd02ec465220", +"bigquery": "15362056f760cad102922b12", "databricks": "47d74d1b45fedd02ec465220", "duckdb": "47d74d1b45fedd02ec465220", "generic": "47d74d1b45fedd02ec465220", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "47d74d1b45fedd02ec465220", +"motherduck": "47d74d1b45fedd02ec465220", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "47d74d1b45fedd02ec465220", "postgresql": "47d74d1b45fedd02ec465220", "snowflake": "47d74d1b45fedd02ec465220", @@ -17994,8 +17994,8 @@ "databricks": "60a23b7dcae6c79f4de83068", "duckdb": "60a23b7dcae6c79f4de83068", "generic": "60a23b7dcae6c79f4de83068", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60a23b7dcae6c79f4de83068", +"motherduck": "60a23b7dcae6c79f4de83068", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "60a23b7dcae6c79f4de83068", "postgresql": "60a23b7dcae6c79f4de83068", "snowflake": "60a23b7dcae6c79f4de83068", @@ -18006,17 +18006,17 @@ "type", { "DuckDB": "1879bc874141d9160106e020", -"None": "c513dcc8ed8dfd8935374a47", -"bigquery": "c513dcc8ed8dfd8935374a47", -"databricks": "c513dcc8ed8dfd8935374a47", +"None": "072b6126112a6217e028fc8f", +"bigquery": "072b6126112a6217e028fc8f", +"databricks": "072b6126112a6217e028fc8f", "duckdb": "1879bc874141d9160106e020", -"generic": "c513dcc8ed8dfd8935374a47", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c513dcc8ed8dfd8935374a47", +"generic": "072b6126112a6217e028fc8f", +"motherduck": "1879bc874141d9160106e020", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c513dcc8ed8dfd8935374a47", "postgresql": "c513dcc8ed8dfd8935374a47", "snowflake": "c513dcc8ed8dfd8935374a47", -"tsql": "c513dcc8ed8dfd8935374a47" +"tsql": "072b6126112a6217e028fc8f" } ], [ @@ -18028,10 +18028,10 @@ "databricks": "2705f19942a4194a72576911", "duckdb": "2705f19942a4194a72576911", "generic": "2705f19942a4194a72576911", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2705f19942a4194a72576911", -"postgres": "2705f19942a4194a72576911", -"postgresql": "2705f19942a4194a72576911", +"motherduck": "2705f19942a4194a72576911", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5226d7e2a62c7650a87d6e84", +"postgresql": "5226d7e2a62c7650a87d6e84", "snowflake": "2705f19942a4194a72576911", "tsql": "2705f19942a4194a72576911" } @@ -18045,8 +18045,8 @@ "databricks": "9b1399d4f1923837359745f8", "duckdb": "9b1399d4f1923837359745f8", "generic": "9b1399d4f1923837359745f8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9b1399d4f1923837359745f8", +"motherduck": "9b1399d4f1923837359745f8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9b1399d4f1923837359745f8", "postgresql": "9b1399d4f1923837359745f8", "snowflake": "9b1399d4f1923837359745f8", @@ -18056,16 +18056,16 @@ [ "type", { -"DuckDB": "c43574432afc90bcba6944b2", -"None": "7040afbb3a088baf55e328b2", -"bigquery": "ba611057434e43344935471b", -"databricks": "ba611057434e43344935471b", -"duckdb": "c43574432afc90bcba6944b2", -"generic": "7040afbb3a088baf55e328b2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "1b3243409566f14d81c11fff", -"postgresql": "1b3243409566f14d81c11fff", +"DuckDB": "83f3a2696c58aa8d709a227a", +"None": "3866c29c7e0df841d464b98c", +"bigquery": "3866c29c7e0df841d464b98c", +"databricks": "3866c29c7e0df841d464b98c", +"duckdb": "83f3a2696c58aa8d709a227a", +"generic": "3866c29c7e0df841d464b98c", +"motherduck": "83f3a2696c58aa8d709a227a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3866c29c7e0df841d464b98c", +"postgresql": "3866c29c7e0df841d464b98c", "snowflake": "06a9af03923de91e5e27860a", "tsql": "3866c29c7e0df841d464b98c" } @@ -18074,17 +18074,17 @@ "type", { "DuckDB": "d229e289e63364bf0d56e920", -"None": "28c3447161aa1c0a550659b8", -"bigquery": "28c3447161aa1c0a550659b8", -"databricks": "b8e50cd0369cf0810e7054be", +"None": "5b121b9b6ea0450c8d33ee34", +"bigquery": "ded028ebfb27c12284a8381a", +"databricks": "5b121b9b6ea0450c8d33ee34", "duckdb": "d229e289e63364bf0d56e920", -"generic": "28c3447161aa1c0a550659b8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28c3447161aa1c0a550659b8", -"postgres": "28c3447161aa1c0a550659b8", -"postgresql": "28c3447161aa1c0a550659b8", +"generic": "5b121b9b6ea0450c8d33ee34", +"motherduck": "d229e289e63364bf0d56e920", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5b121b9b6ea0450c8d33ee34", +"postgresql": "5b121b9b6ea0450c8d33ee34", "snowflake": "d90e8915a295b2cd5e690bd1", -"tsql": "28c3447161aa1c0a550659b8" +"tsql": "5b121b9b6ea0450c8d33ee34" } ], [ @@ -18096,10 +18096,10 @@ "databricks": "a3acd7ba35e81a800f967bd3", "duckdb": "42d4a23d9a52e79d0c3458d3", "generic": "a3acd7ba35e81a800f967bd3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a3acd7ba35e81a800f967bd3", -"postgres": "a3acd7ba35e81a800f967bd3", -"postgresql": "a3acd7ba35e81a800f967bd3", +"motherduck": "42d4a23d9a52e79d0c3458d3", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "42d4a23d9a52e79d0c3458d3", +"postgresql": "42d4a23d9a52e79d0c3458d3", "snowflake": "a3acd7ba35e81a800f967bd3", "tsql": "a3acd7ba35e81a800f967bd3" } @@ -18113,10 +18113,10 @@ "databricks": "c6106f30f23f392e17eadef4", "duckdb": "c6106f30f23f392e17eadef4", "generic": "c6106f30f23f392e17eadef4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6106f30f23f392e17eadef4", -"postgres": "c6106f30f23f392e17eadef4", -"postgresql": "c6106f30f23f392e17eadef4", +"motherduck": "c6106f30f23f392e17eadef4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f43dac38f34a1c0618a3f812", +"postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "c6106f30f23f392e17eadef4", "tsql": "c6106f30f23f392e17eadef4" } @@ -18130,10 +18130,10 @@ "databricks": "804fec6876d0b43935790142", "duckdb": "804fec6876d0b43935790142", "generic": "804fec6876d0b43935790142", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "804fec6876d0b43935790142", -"postgres": "804fec6876d0b43935790142", -"postgresql": "804fec6876d0b43935790142", +"motherduck": "804fec6876d0b43935790142", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "191fc59c754706512337edc1", +"postgresql": "191fc59c754706512337edc1", "snowflake": "804fec6876d0b43935790142", "tsql": "804fec6876d0b43935790142" } @@ -18147,8 +18147,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -18164,10 +18164,10 @@ "databricks": "7b87db889221e05e13ec3845", "duckdb": "194c385aa00f52f45426748f", "generic": "7b87db889221e05e13ec3845", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7b87db889221e05e13ec3845", -"postgres": "7b87db889221e05e13ec3845", -"postgresql": "7b87db889221e05e13ec3845", +"motherduck": "194c385aa00f52f45426748f", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "07fffe8743dfd4e88e6b3f18", +"postgresql": "07fffe8743dfd4e88e6b3f18", "snowflake": "7b87db889221e05e13ec3845", "tsql": "7b87db889221e05e13ec3845" } @@ -18181,10 +18181,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -18198,12 +18198,12 @@ "databricks": "eb72e9a8e16bab9ee01146ee", "duckdb": "c69a366e715a81bca598d475", "generic": "c69a366e715a81bca598d475", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c69a366e715a81bca598d475", +"motherduck": "c69a366e715a81bca598d475", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c69a366e715a81bca598d475", "postgresql": "c69a366e715a81bca598d475", "snowflake": "f41d94b0bed9910074641da6", -"tsql": "c69a366e715a81bca598d475" +"tsql": "c036322107e1c935c7bc6edd" } ], [ @@ -18211,12 +18211,12 @@ { "DuckDB": "558da38ec4d9d5d6c72f60cf", "None": "558da38ec4d9d5d6c72f60cf", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "558da38ec4d9d5d6c72f60cf", "generic": "558da38ec4d9d5d6c72f60cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "558da38ec4d9d5d6c72f60cf", +"motherduck": "558da38ec4d9d5d6c72f60cf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "558da38ec4d9d5d6c72f60cf", "postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "558da38ec4d9d5d6c72f60cf", @@ -18232,8 +18232,8 @@ "databricks": "b210f8eb26c37b422f786c6b", "duckdb": "b210f8eb26c37b422f786c6b", "generic": "b210f8eb26c37b422f786c6b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b210f8eb26c37b422f786c6b", +"motherduck": "b210f8eb26c37b422f786c6b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b210f8eb26c37b422f786c6b", "postgresql": "b210f8eb26c37b422f786c6b", "snowflake": "b210f8eb26c37b422f786c6b", @@ -18249,10 +18249,10 @@ "databricks": "279ea7f6f6a3910de04b795b", "duckdb": "279ea7f6f6a3910de04b795b", "generic": "279ea7f6f6a3910de04b795b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "279ea7f6f6a3910de04b795b", -"postgres": "279ea7f6f6a3910de04b795b", -"postgresql": "279ea7f6f6a3910de04b795b", +"motherduck": "279ea7f6f6a3910de04b795b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bc520aebd5872e40030ec7d9", +"postgresql": "bc520aebd5872e40030ec7d9", "snowflake": "279ea7f6f6a3910de04b795b", "tsql": "279ea7f6f6a3910de04b795b" } @@ -18263,11 +18263,11 @@ "DuckDB": "92e8caae709d951bb8ed59cb", "None": "92e8caae709d951bb8ed59cb", "bigquery": "92e8caae709d951bb8ed59cb", -"databricks": "1758ac00dad1bae84521b3fb", +"databricks": "92e8caae709d951bb8ed59cb", "duckdb": "92e8caae709d951bb8ed59cb", "generic": "92e8caae709d951bb8ed59cb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1758ac00dad1bae84521b3fb", +"motherduck": "92e8caae709d951bb8ed59cb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "92e8caae709d951bb8ed59cb", "postgresql": "92e8caae709d951bb8ed59cb", "snowflake": "92e8caae709d951bb8ed59cb", @@ -18283,8 +18283,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -18300,10 +18300,10 @@ "databricks": "a686bb6db17ab079cfc79224", "duckdb": "a686bb6db17ab079cfc79224", "generic": "a686bb6db17ab079cfc79224", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a686bb6db17ab079cfc79224", -"postgres": "a686bb6db17ab079cfc79224", -"postgresql": "a686bb6db17ab079cfc79224", +"motherduck": "a686bb6db17ab079cfc79224", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "74a30f6aa9def0b58afc616c", +"postgresql": "74a30f6aa9def0b58afc616c", "snowflake": "1096c3c8687c8e8eb538ff74", "tsql": "a686bb6db17ab079cfc79224" } @@ -18317,10 +18317,10 @@ "databricks": "56f06885daf94b4fcc0a624b", "duckdb": "56f06885daf94b4fcc0a624b", "generic": "56f06885daf94b4fcc0a624b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "56f06885daf94b4fcc0a624b", -"postgres": "56f06885daf94b4fcc0a624b", -"postgresql": "56f06885daf94b4fcc0a624b", +"motherduck": "56f06885daf94b4fcc0a624b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "d8c1f10bbe9d46ecb6ee0bc7", +"postgresql": "d8c1f10bbe9d46ecb6ee0bc7", "snowflake": "56f06885daf94b4fcc0a624b", "tsql": "56f06885daf94b4fcc0a624b" } @@ -18334,8 +18334,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -18351,8 +18351,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -18368,10 +18368,10 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "396b2ce32efb5365b393c230", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", -"postgres": "e57ac1541fb51b362cd5b470", -"postgresql": "e57ac1541fb51b362cd5b470", +"motherduck": "396b2ce32efb5365b393c230", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "396b2ce32efb5365b393c230", +"postgresql": "396b2ce32efb5365b393c230", "snowflake": "4b356542044f1267fcfc487a", "tsql": "e57ac1541fb51b362cd5b470" } @@ -18385,8 +18385,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "305aaf0b95cee629dd46380c", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "305aaf0b95cee629dd46380c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -18402,10 +18402,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -18419,10 +18419,10 @@ "databricks": "d2c97d221599d038d9875873", "duckdb": "f7fca17b74cc50ae3de8c826", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -18436,8 +18436,8 @@ "databricks": "d1dc9a140dbe2eb1874c6ad7", "duckdb": "d1dc9a140dbe2eb1874c6ad7", "generic": "d1dc9a140dbe2eb1874c6ad7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d1dc9a140dbe2eb1874c6ad7", +"motherduck": "d1dc9a140dbe2eb1874c6ad7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d1dc9a140dbe2eb1874c6ad7", "postgresql": "d1dc9a140dbe2eb1874c6ad7", "snowflake": "d1dc9a140dbe2eb1874c6ad7", @@ -18453,10 +18453,10 @@ "databricks": "4c78bdde0b2dfd77ab71c9e8", "duckdb": "4c78bdde0b2dfd77ab71c9e8", "generic": "4c78bdde0b2dfd77ab71c9e8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4c78bdde0b2dfd77ab71c9e8", -"postgres": "4c78bdde0b2dfd77ab71c9e8", -"postgresql": "4c78bdde0b2dfd77ab71c9e8", +"motherduck": "4c78bdde0b2dfd77ab71c9e8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e802515d969e770f686478b5", +"postgresql": "e802515d969e770f686478b5", "snowflake": "4c78bdde0b2dfd77ab71c9e8", "tsql": "4c78bdde0b2dfd77ab71c9e8" } @@ -18470,8 +18470,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -18487,8 +18487,8 @@ "databricks": "8a3f094f18fb4047f0febf59", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "8a3f094f18fb4047f0febf59", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8a3f094f18fb4047f0febf59", "postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "850deea990b52b82b36a9848", @@ -18499,17 +18499,17 @@ "type", { "DuckDB": "f46cf4b4e131aa122b5b33d6", -"None": "f46cf4b4e131aa122b5b33d6", -"bigquery": "f46cf4b4e131aa122b5b33d6", -"databricks": "f46cf4b4e131aa122b5b33d6", +"None": "f774d5e8feadbf11edaa26b1", +"bigquery": "9bafb08d41fd1c208b5deca4", +"databricks": "f774d5e8feadbf11edaa26b1", "duckdb": "f46cf4b4e131aa122b5b33d6", -"generic": "f46cf4b4e131aa122b5b33d6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f46cf4b4e131aa122b5b33d6", +"generic": "f774d5e8feadbf11edaa26b1", +"motherduck": "f46cf4b4e131aa122b5b33d6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f46cf4b4e131aa122b5b33d6", "postgresql": "f46cf4b4e131aa122b5b33d6", "snowflake": "f46cf4b4e131aa122b5b33d6", -"tsql": "f46cf4b4e131aa122b5b33d6" +"tsql": "f774d5e8feadbf11edaa26b1" } ], [ @@ -18521,8 +18521,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -18534,14 +18534,14 @@ { "DuckDB": "6eb49ceb88fcb2297831ca05", "None": "6eb49ceb88fcb2297831ca05", -"bigquery": "6eb49ceb88fcb2297831ca05", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "6eb49ceb88fcb2297831ca05", "duckdb": "6eb49ceb88fcb2297831ca05", "generic": "6eb49ceb88fcb2297831ca05", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6eb49ceb88fcb2297831ca05", -"postgres": "6eb49ceb88fcb2297831ca05", -"postgresql": "6eb49ceb88fcb2297831ca05", +"motherduck": "6eb49ceb88fcb2297831ca05", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "623e22ea8921c9e57a15475b", +"postgresql": "623e22ea8921c9e57a15475b", "snowflake": "6eb49ceb88fcb2297831ca05", "tsql": "6eb49ceb88fcb2297831ca05" } @@ -18555,8 +18555,8 @@ "databricks": "853b2c0ad6ea4565b1e301e8", "duckdb": "853b2c0ad6ea4565b1e301e8", "generic": "853b2c0ad6ea4565b1e301e8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "853b2c0ad6ea4565b1e301e8", +"motherduck": "853b2c0ad6ea4565b1e301e8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "853b2c0ad6ea4565b1e301e8", "postgresql": "853b2c0ad6ea4565b1e301e8", "snowflake": "853b2c0ad6ea4565b1e301e8", @@ -18572,10 +18572,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -18584,17 +18584,17 @@ "type", { "DuckDB": "1558a2a87f510cca4baaad5b", -"None": "1558a2a87f510cca4baaad5b", -"bigquery": "1558a2a87f510cca4baaad5b", -"databricks": "1558a2a87f510cca4baaad5b", +"None": "5865b96f5136d463181f63e4", +"bigquery": "5865b96f5136d463181f63e4", +"databricks": "5865b96f5136d463181f63e4", "duckdb": "1558a2a87f510cca4baaad5b", -"generic": "1558a2a87f510cca4baaad5b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1558a2a87f510cca4baaad5b", +"generic": "5865b96f5136d463181f63e4", +"motherduck": "1558a2a87f510cca4baaad5b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1558a2a87f510cca4baaad5b", "postgresql": "1558a2a87f510cca4baaad5b", "snowflake": "67b5874a2c569226c490211f", -"tsql": "1558a2a87f510cca4baaad5b" +"tsql": "5865b96f5136d463181f63e4" } ], [ @@ -18606,8 +18606,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -18623,8 +18623,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -18640,8 +18640,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -18657,8 +18657,8 @@ "databricks": "d043825e95555167dc033b5f", "duckdb": "d043825e95555167dc033b5f", "generic": "d043825e95555167dc033b5f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d043825e95555167dc033b5f", +"motherduck": "d043825e95555167dc033b5f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d043825e95555167dc033b5f", "postgresql": "d043825e95555167dc033b5f", "snowflake": "d043825e95555167dc033b5f", @@ -18674,8 +18674,8 @@ "databricks": "84e86bec7646b6ad19ceef59", "duckdb": "6a2162b5abe5a73734c47041", "generic": "6a2162b5abe5a73734c47041", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6a2162b5abe5a73734c47041", +"motherduck": "6a2162b5abe5a73734c47041", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6a2162b5abe5a73734c47041", "postgresql": "6a2162b5abe5a73734c47041", "snowflake": "16d902e899040d0c0c9dcf58", @@ -18691,8 +18691,8 @@ "databricks": "56245289768b0fb155df9ffd", "duckdb": "56245289768b0fb155df9ffd", "generic": "56245289768b0fb155df9ffd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "56245289768b0fb155df9ffd", +"motherduck": "56245289768b0fb155df9ffd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "56245289768b0fb155df9ffd", "postgresql": "56245289768b0fb155df9ffd", "snowflake": "56245289768b0fb155df9ffd", @@ -18708,8 +18708,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -18725,8 +18725,8 @@ "databricks": "362b5422259610d45387d0ab", "duckdb": "362b5422259610d45387d0ab", "generic": "362b5422259610d45387d0ab", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "362b5422259610d45387d0ab", +"motherduck": "362b5422259610d45387d0ab", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "362b5422259610d45387d0ab", "postgresql": "362b5422259610d45387d0ab", "snowflake": "362b5422259610d45387d0ab", @@ -18742,8 +18742,8 @@ "databricks": "b2c9b6ebca6c097f3caa0aa8", "duckdb": "b2c9b6ebca6c097f3caa0aa8", "generic": "b2c9b6ebca6c097f3caa0aa8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b2c9b6ebca6c097f3caa0aa8", +"motherduck": "b2c9b6ebca6c097f3caa0aa8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b2c9b6ebca6c097f3caa0aa8", "postgresql": "b2c9b6ebca6c097f3caa0aa8", "snowflake": "b2c9b6ebca6c097f3caa0aa8", @@ -18759,8 +18759,8 @@ "databricks": "b2c07944c578e7e9c9297bd8", "duckdb": "25f9faed41d5d23ca3fa8175", "generic": "25f9faed41d5d23ca3fa8175", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "25f9faed41d5d23ca3fa8175", +"motherduck": "25f9faed41d5d23ca3fa8175", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "25f9faed41d5d23ca3fa8175", "postgresql": "25f9faed41d5d23ca3fa8175", "snowflake": "fc6c5aa7025d9a737c71c606", @@ -18776,8 +18776,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "78b2cf275c640af54feeca31", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78b2cf275c640af54feeca31", "postgresql": "78b2cf275c640af54feeca31", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -18793,8 +18793,8 @@ "databricks": "f7fca17b74cc50ae3de8c826", "duckdb": "f7fca17b74cc50ae3de8c826", "generic": "f7fca17b74cc50ae3de8c826", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f7fca17b74cc50ae3de8c826", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f7fca17b74cc50ae3de8c826", "postgresql": "f7fca17b74cc50ae3de8c826", "snowflake": "55155766823b6a3a5e882fc2", @@ -18810,8 +18810,8 @@ "databricks": "e7a76004da6461a939ce5fef", "duckdb": "e7a76004da6461a939ce5fef", "generic": "e7a76004da6461a939ce5fef", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e7a76004da6461a939ce5fef", +"motherduck": "e7a76004da6461a939ce5fef", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e7a76004da6461a939ce5fef", "postgresql": "e7a76004da6461a939ce5fef", "snowflake": "e7a76004da6461a939ce5fef", @@ -18827,8 +18827,8 @@ "databricks": "60a23b7dcae6c79f4de83068", "duckdb": "60a23b7dcae6c79f4de83068", "generic": "60a23b7dcae6c79f4de83068", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60a23b7dcae6c79f4de83068", +"motherduck": "60a23b7dcae6c79f4de83068", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "60a23b7dcae6c79f4de83068", "postgresql": "60a23b7dcae6c79f4de83068", "snowflake": "60a23b7dcae6c79f4de83068", @@ -18844,8 +18844,8 @@ "databricks": "a47af75c6582f749e1d2b2e7", "duckdb": "a47af75c6582f749e1d2b2e7", "generic": "a47af75c6582f749e1d2b2e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a47af75c6582f749e1d2b2e7", +"motherduck": "a47af75c6582f749e1d2b2e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a47af75c6582f749e1d2b2e7", "postgresql": "a47af75c6582f749e1d2b2e7", "snowflake": "a47af75c6582f749e1d2b2e7", @@ -18861,8 +18861,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -18873,17 +18873,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -18895,8 +18895,8 @@ "databricks": "e1c74d96ac578f9323bf1bbd", "duckdb": "e1c74d96ac578f9323bf1bbd", "generic": "e1c74d96ac578f9323bf1bbd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e1c74d96ac578f9323bf1bbd", +"motherduck": "e1c74d96ac578f9323bf1bbd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e1c74d96ac578f9323bf1bbd", "postgresql": "e1c74d96ac578f9323bf1bbd", "snowflake": "e1c74d96ac578f9323bf1bbd", @@ -18912,8 +18912,8 @@ "databricks": "200c00626c910f6fd6177bb5", "duckdb": "200c00626c910f6fd6177bb5", "generic": "200c00626c910f6fd6177bb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "200c00626c910f6fd6177bb5", +"motherduck": "200c00626c910f6fd6177bb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "200c00626c910f6fd6177bb5", "postgresql": "200c00626c910f6fd6177bb5", "snowflake": "200c00626c910f6fd6177bb5", @@ -18929,8 +18929,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -18946,8 +18946,8 @@ "databricks": "fe0bedb7dda0e9247a51fdee", "duckdb": "fe0bedb7dda0e9247a51fdee", "generic": "fe0bedb7dda0e9247a51fdee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe0bedb7dda0e9247a51fdee", +"motherduck": "fe0bedb7dda0e9247a51fdee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fe0bedb7dda0e9247a51fdee", "postgresql": "fe0bedb7dda0e9247a51fdee", "snowflake": "fe0bedb7dda0e9247a51fdee", @@ -18963,8 +18963,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -18980,8 +18980,8 @@ "databricks": "c6889f2965db73017be6f6e7", "duckdb": "c6889f2965db73017be6f6e7", "generic": "c6889f2965db73017be6f6e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6889f2965db73017be6f6e7", +"motherduck": "c6889f2965db73017be6f6e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6889f2965db73017be6f6e7", "postgresql": "c6889f2965db73017be6f6e7", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -18997,10 +18997,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "aed0419e7f2ce7c0a8892248", "generic": "aed0419e7f2ce7c0a8892248", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aed0419e7f2ce7c0a8892248", -"postgres": "19e127c23b4598727a2d8ceb", -"postgresql": "19e127c23b4598727a2d8ceb", +"motherduck": "aed0419e7f2ce7c0a8892248", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "aed0419e7f2ce7c0a8892248", "tsql": "aed0419e7f2ce7c0a8892248" } @@ -19014,8 +19014,8 @@ "databricks": "55a086bc8305ad303017ae10", "duckdb": "55a086bc8305ad303017ae10", "generic": "55a086bc8305ad303017ae10", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "55a086bc8305ad303017ae10", +"motherduck": "55a086bc8305ad303017ae10", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "55a086bc8305ad303017ae10", "postgresql": "55a086bc8305ad303017ae10", "snowflake": "55a086bc8305ad303017ae10", @@ -19031,10 +19031,10 @@ "databricks": "d08ef9483feed34fc0ee4319", "duckdb": "f7598fbf399afabe1e01a358", "generic": "d08ef9483feed34fc0ee4319", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d08ef9483feed34fc0ee4319", -"postgres": "d08ef9483feed34fc0ee4319", -"postgresql": "d08ef9483feed34fc0ee4319", +"motherduck": "f7598fbf399afabe1e01a358", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f7598fbf399afabe1e01a358", +"postgresql": "f7598fbf399afabe1e01a358", "snowflake": "d08ef9483feed34fc0ee4319", "tsql": "d08ef9483feed34fc0ee4319" } @@ -19048,8 +19048,8 @@ "databricks": "d3b6a6835220f9cedfa363c7", "duckdb": "d3b6a6835220f9cedfa363c7", "generic": "d3b6a6835220f9cedfa363c7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d3b6a6835220f9cedfa363c7", +"motherduck": "d3b6a6835220f9cedfa363c7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d3b6a6835220f9cedfa363c7", "postgresql": "d3b6a6835220f9cedfa363c7", "snowflake": "d3b6a6835220f9cedfa363c7", @@ -19065,8 +19065,8 @@ "databricks": "3d23f811a03db519b598c815", "duckdb": "3d23f811a03db519b598c815", "generic": "3d23f811a03db519b598c815", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3d23f811a03db519b598c815", +"motherduck": "3d23f811a03db519b598c815", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3d23f811a03db519b598c815", "postgresql": "3d23f811a03db519b598c815", "snowflake": "3d23f811a03db519b598c815", @@ -19082,8 +19082,8 @@ "databricks": "f816d7b3b97815f308346e20", "duckdb": "f816d7b3b97815f308346e20", "generic": "f816d7b3b97815f308346e20", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f816d7b3b97815f308346e20", +"motherduck": "f816d7b3b97815f308346e20", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f816d7b3b97815f308346e20", "postgresql": "f816d7b3b97815f308346e20", "snowflake": "f816d7b3b97815f308346e20", @@ -19099,10 +19099,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "aed0419e7f2ce7c0a8892248", "generic": "aed0419e7f2ce7c0a8892248", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aed0419e7f2ce7c0a8892248", -"postgres": "19e127c23b4598727a2d8ceb", -"postgresql": "19e127c23b4598727a2d8ceb", +"motherduck": "aed0419e7f2ce7c0a8892248", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "aed0419e7f2ce7c0a8892248", "tsql": "aed0419e7f2ce7c0a8892248" } @@ -19116,8 +19116,8 @@ "databricks": "57922064a7dc546cbb50e42b", "duckdb": "57922064a7dc546cbb50e42b", "generic": "57922064a7dc546cbb50e42b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "57922064a7dc546cbb50e42b", +"motherduck": "57922064a7dc546cbb50e42b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "57922064a7dc546cbb50e42b", "postgresql": "57922064a7dc546cbb50e42b", "snowflake": "57922064a7dc546cbb50e42b", @@ -19128,17 +19128,17 @@ "type", { "DuckDB": "7b2fd741638cbd96bb38d7e0", -"None": "7b2fd741638cbd96bb38d7e0", -"bigquery": "7b2fd741638cbd96bb38d7e0", -"databricks": "4fb572024031cb7631777be1", +"None": "1e765712a4c0f6d3649350c6", +"bigquery": "1e765712a4c0f6d3649350c6", +"databricks": "1e765712a4c0f6d3649350c6", "duckdb": "7b2fd741638cbd96bb38d7e0", -"generic": "7b2fd741638cbd96bb38d7e0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7b2fd741638cbd96bb38d7e0", -"postgres": "7b2fd741638cbd96bb38d7e0", -"postgresql": "7b2fd741638cbd96bb38d7e0", +"generic": "1e765712a4c0f6d3649350c6", +"motherduck": "7b2fd741638cbd96bb38d7e0", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "1e765712a4c0f6d3649350c6", +"postgresql": "1e765712a4c0f6d3649350c6", "snowflake": "7b2fd741638cbd96bb38d7e0", -"tsql": "7b2fd741638cbd96bb38d7e0" +"tsql": "1e765712a4c0f6d3649350c6" } ], [ @@ -19150,8 +19150,8 @@ "databricks": "e4f0244beaa01667e80482b6", "duckdb": "a766fea06fc070b352976b0a", "generic": "a766fea06fc070b352976b0a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e4f0244beaa01667e80482b6", +"motherduck": "a766fea06fc070b352976b0a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a766fea06fc070b352976b0a", "postgresql": "a766fea06fc070b352976b0a", "snowflake": "a766fea06fc070b352976b0a", @@ -19167,8 +19167,8 @@ "databricks": "15b3387f6c4d73bcf113983a", "duckdb": "15b3387f6c4d73bcf113983a", "generic": "15b3387f6c4d73bcf113983a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "15b3387f6c4d73bcf113983a", +"motherduck": "15b3387f6c4d73bcf113983a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "15b3387f6c4d73bcf113983a", "postgresql": "15b3387f6c4d73bcf113983a", "snowflake": "15b3387f6c4d73bcf113983a", @@ -19181,11 +19181,11 @@ "DuckDB": "d34f84c1b71b9c9c0c20abb0", "None": "d34f84c1b71b9c9c0c20abb0", "bigquery": "d34f84c1b71b9c9c0c20abb0", -"databricks": "c662d9470aa4255dfb493df1", +"databricks": "d34f84c1b71b9c9c0c20abb0", "duckdb": "d34f84c1b71b9c9c0c20abb0", "generic": "d34f84c1b71b9c9c0c20abb0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c662d9470aa4255dfb493df1", +"motherduck": "d34f84c1b71b9c9c0c20abb0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d34f84c1b71b9c9c0c20abb0", "postgresql": "d34f84c1b71b9c9c0c20abb0", "snowflake": "d34f84c1b71b9c9c0c20abb0", @@ -19198,11 +19198,11 @@ "DuckDB": "14cd6e78d75f61b2bd1bbd71", "None": "14cd6e78d75f61b2bd1bbd71", "bigquery": "14cd6e78d75f61b2bd1bbd71", -"databricks": "73504f31156e8e661c1fb26c", +"databricks": "14cd6e78d75f61b2bd1bbd71", "duckdb": "14cd6e78d75f61b2bd1bbd71", "generic": "14cd6e78d75f61b2bd1bbd71", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73504f31156e8e661c1fb26c", +"motherduck": "14cd6e78d75f61b2bd1bbd71", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "14cd6e78d75f61b2bd1bbd71", "postgresql": "14cd6e78d75f61b2bd1bbd71", "snowflake": "14cd6e78d75f61b2bd1bbd71", @@ -19218,8 +19218,8 @@ "databricks": "96e8f438bf42d79cd8b1fada", "duckdb": "96e8f438bf42d79cd8b1fada", "generic": "96e8f438bf42d79cd8b1fada", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "96e8f438bf42d79cd8b1fada", +"motherduck": "96e8f438bf42d79cd8b1fada", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "96e8f438bf42d79cd8b1fada", "postgresql": "96e8f438bf42d79cd8b1fada", "snowflake": "96e8f438bf42d79cd8b1fada", @@ -19235,8 +19235,8 @@ "databricks": "260b29f255364b2044abe127", "duckdb": "260b29f255364b2044abe127", "generic": "260b29f255364b2044abe127", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "260b29f255364b2044abe127", +"motherduck": "260b29f255364b2044abe127", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "260b29f255364b2044abe127", "postgresql": "260b29f255364b2044abe127", "snowflake": "260b29f255364b2044abe127", @@ -19248,12 +19248,12 @@ { "DuckDB": "e5199d22ebbd1383c1e821df", "None": "e5199d22ebbd1383c1e821df", -"bigquery": "e5199d22ebbd1383c1e821df", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "e5199d22ebbd1383c1e821df", "duckdb": "e5199d22ebbd1383c1e821df", "generic": "e5199d22ebbd1383c1e821df", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e5199d22ebbd1383c1e821df", +"motherduck": "e5199d22ebbd1383c1e821df", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e5199d22ebbd1383c1e821df", "postgresql": "e5199d22ebbd1383c1e821df", "snowflake": "e5199d22ebbd1383c1e821df", @@ -19269,10 +19269,10 @@ "databricks": "336fd1b7a1f7d8105c29280c", "duckdb": "0baaca265927140414f32b37", "generic": "336fd1b7a1f7d8105c29280c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "336fd1b7a1f7d8105c29280c", -"postgres": "336fd1b7a1f7d8105c29280c", -"postgresql": "336fd1b7a1f7d8105c29280c", +"motherduck": "0baaca265927140414f32b37", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9ea4932505663b43f527adad", +"postgresql": "9ea4932505663b43f527adad", "snowflake": "336fd1b7a1f7d8105c29280c", "tsql": "336fd1b7a1f7d8105c29280c" } @@ -19286,8 +19286,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -19303,8 +19303,8 @@ "databricks": "802f1c1fcb97ddd5679172d2", "duckdb": "802f1c1fcb97ddd5679172d2", "generic": "802f1c1fcb97ddd5679172d2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "802f1c1fcb97ddd5679172d2", +"motherduck": "802f1c1fcb97ddd5679172d2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "802f1c1fcb97ddd5679172d2", "postgresql": "802f1c1fcb97ddd5679172d2", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -19320,8 +19320,8 @@ "databricks": "d91c8f17eb347629690400c7", "duckdb": "d91c8f17eb347629690400c7", "generic": "d91c8f17eb347629690400c7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d91c8f17eb347629690400c7", +"motherduck": "d91c8f17eb347629690400c7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d91c8f17eb347629690400c7", "postgresql": "d91c8f17eb347629690400c7", "snowflake": "d91c8f17eb347629690400c7", @@ -19337,8 +19337,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -19349,17 +19349,17 @@ "type", { "DuckDB": "314ed506f24e92300a425780", -"None": "314ed506f24e92300a425780", -"bigquery": "314ed506f24e92300a425780", -"databricks": "314ed506f24e92300a425780", +"None": "9a66317fb8d040bf991502c0", +"bigquery": "9bafb08d41fd1c208b5deca4", +"databricks": "9a66317fb8d040bf991502c0", "duckdb": "314ed506f24e92300a425780", -"generic": "314ed506f24e92300a425780", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "314ed506f24e92300a425780", +"generic": "9a66317fb8d040bf991502c0", +"motherduck": "314ed506f24e92300a425780", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "314ed506f24e92300a425780", "postgresql": "314ed506f24e92300a425780", "snowflake": "314ed506f24e92300a425780", -"tsql": "314ed506f24e92300a425780" +"tsql": "9a66317fb8d040bf991502c0" } ], [ @@ -19371,8 +19371,8 @@ "databricks": "2873244018c69d6d42752bdc", "duckdb": "2873244018c69d6d42752bdc", "generic": "2873244018c69d6d42752bdc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2873244018c69d6d42752bdc", +"motherduck": "2873244018c69d6d42752bdc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2873244018c69d6d42752bdc", "postgresql": "2873244018c69d6d42752bdc", "snowflake": "2873244018c69d6d42752bdc", @@ -19388,8 +19388,8 @@ "databricks": "3d23f811a03db519b598c815", "duckdb": "3d23f811a03db519b598c815", "generic": "3d23f811a03db519b598c815", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3d23f811a03db519b598c815", +"motherduck": "3d23f811a03db519b598c815", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3d23f811a03db519b598c815", "postgresql": "3d23f811a03db519b598c815", "snowflake": "3d23f811a03db519b598c815", @@ -19400,17 +19400,17 @@ "type", { "DuckDB": "087a9417cb511ff7bcfd6ccb", -"None": "383f8922f4b6f167d384037d", -"bigquery": "383f8922f4b6f167d384037d", -"databricks": "383f8922f4b6f167d384037d", +"None": "2e8f6dfec4597fb9373329e7", +"bigquery": "2e8f6dfec4597fb9373329e7", +"databricks": "2e8f6dfec4597fb9373329e7", "duckdb": "087a9417cb511ff7bcfd6ccb", -"generic": "383f8922f4b6f167d384037d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "383f8922f4b6f167d384037d", +"generic": "2e8f6dfec4597fb9373329e7", +"motherduck": "087a9417cb511ff7bcfd6ccb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "383f8922f4b6f167d384037d", "postgresql": "383f8922f4b6f167d384037d", "snowflake": "383f8922f4b6f167d384037d", -"tsql": "383f8922f4b6f167d384037d" +"tsql": "2e8f6dfec4597fb9373329e7" } ], [ @@ -19422,10 +19422,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -19435,12 +19435,12 @@ { "DuckDB": "b017fb3c48f125e51b6db123", "None": "b017fb3c48f125e51b6db123", -"bigquery": "b017fb3c48f125e51b6db123", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "b017fb3c48f125e51b6db123", "duckdb": "b017fb3c48f125e51b6db123", "generic": "b017fb3c48f125e51b6db123", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b017fb3c48f125e51b6db123", +"motherduck": "b017fb3c48f125e51b6db123", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b017fb3c48f125e51b6db123", "postgresql": "b017fb3c48f125e51b6db123", "snowflake": "b017fb3c48f125e51b6db123", @@ -19456,8 +19456,8 @@ "databricks": "a7120d1b1abcb844c87a968a", "duckdb": "a7120d1b1abcb844c87a968a", "generic": "a7120d1b1abcb844c87a968a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a7120d1b1abcb844c87a968a", +"motherduck": "a7120d1b1abcb844c87a968a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a7120d1b1abcb844c87a968a", "postgresql": "a7120d1b1abcb844c87a968a", "snowflake": "a7120d1b1abcb844c87a968a", @@ -19473,8 +19473,8 @@ "databricks": "fd31c8e468772027804119ec", "duckdb": "fd31c8e468772027804119ec", "generic": "fd31c8e468772027804119ec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fd31c8e468772027804119ec", +"motherduck": "fd31c8e468772027804119ec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fd31c8e468772027804119ec", "postgresql": "fd31c8e468772027804119ec", "snowflake": "fd31c8e468772027804119ec", @@ -19490,8 +19490,8 @@ "databricks": "4169d9651c2a3667fc561f32", "duckdb": "4169d9651c2a3667fc561f32", "generic": "4169d9651c2a3667fc561f32", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4169d9651c2a3667fc561f32", +"motherduck": "4169d9651c2a3667fc561f32", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4169d9651c2a3667fc561f32", "postgresql": "4169d9651c2a3667fc561f32", "snowflake": "b210f8eb26c37b422f786c6b", @@ -19507,8 +19507,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -19524,10 +19524,10 @@ "databricks": "eed0645c1e86fa1a6e35ccaa", "duckdb": "eed0645c1e86fa1a6e35ccaa", "generic": "eed0645c1e86fa1a6e35ccaa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "eed0645c1e86fa1a6e35ccaa", -"postgres": "eed0645c1e86fa1a6e35ccaa", -"postgresql": "eed0645c1e86fa1a6e35ccaa", +"motherduck": "eed0645c1e86fa1a6e35ccaa", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b0477b36767a62145aa9158c", +"postgresql": "b0477b36767a62145aa9158c", "snowflake": "97a20344d811fb77c7690863", "tsql": "eed0645c1e86fa1a6e35ccaa" } @@ -19537,14 +19537,14 @@ { "DuckDB": "3a7ffeb8f81df6ccd57f3689", "None": "3a7ffeb8f81df6ccd57f3689", -"bigquery": "3a7ffeb8f81df6ccd57f3689", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "3a7ffeb8f81df6ccd57f3689", "duckdb": "3a7ffeb8f81df6ccd57f3689", "generic": "3a7ffeb8f81df6ccd57f3689", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3a7ffeb8f81df6ccd57f3689", -"postgres": "3a7ffeb8f81df6ccd57f3689", -"postgresql": "3a7ffeb8f81df6ccd57f3689", +"motherduck": "3a7ffeb8f81df6ccd57f3689", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "062e51ec86ccd542eb78500d", +"postgresql": "062e51ec86ccd542eb78500d", "snowflake": "3a7ffeb8f81df6ccd57f3689", "tsql": "3a7ffeb8f81df6ccd57f3689" } @@ -19558,8 +19558,8 @@ "databricks": "e1c74d96ac578f9323bf1bbd", "duckdb": "e1c74d96ac578f9323bf1bbd", "generic": "e1c74d96ac578f9323bf1bbd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e1c74d96ac578f9323bf1bbd", +"motherduck": "e1c74d96ac578f9323bf1bbd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e1c74d96ac578f9323bf1bbd", "postgresql": "e1c74d96ac578f9323bf1bbd", "snowflake": "e1c74d96ac578f9323bf1bbd", @@ -19569,16 +19569,16 @@ [ "type", { -"DuckDB": "e69f24e5693aeece1e1a03b8", -"None": "6dd6559eebacf5e375e1cce3", -"bigquery": "3e76afa61378ff263b617c7e", -"databricks": "e4317ab2bb471f29ee3c44be", -"duckdb": "e69f24e5693aeece1e1a03b8", -"generic": "6dd6559eebacf5e375e1cce3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6dd6559eebacf5e375e1cce3", -"postgres": "6dd6559eebacf5e375e1cce3", -"postgresql": "6dd6559eebacf5e375e1cce3", +"DuckDB": "427810237ffbfe3ee6dbe0d4", +"None": "6f2f02617b722e48d3f365c2", +"bigquery": "6f2f02617b722e48d3f365c2", +"databricks": "69ec26c1982add4704b948a6", +"duckdb": "427810237ffbfe3ee6dbe0d4", +"generic": "6f2f02617b722e48d3f365c2", +"motherduck": "427810237ffbfe3ee6dbe0d4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "6f2f02617b722e48d3f365c2", +"postgresql": "6f2f02617b722e48d3f365c2", "snowflake": "a4325ee9f4d61463eb81badb", "tsql": "6f2f02617b722e48d3f365c2" } @@ -19592,8 +19592,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -19605,14 +19605,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -19626,10 +19626,10 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "b8321619b794db091c9e96a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b8321619b794db091c9e96a1", -"postgres": "b8321619b794db091c9e96a1", -"postgresql": "b8321619b794db091c9e96a1", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bce26544873d6ec9191c9a86", +"postgresql": "bce26544873d6ec9191c9a86", "snowflake": "b8321619b794db091c9e96a1", "tsql": "b8321619b794db091c9e96a1" } @@ -19643,8 +19643,8 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19a1a80ffc06169f32694de8", "postgresql": "19a1a80ffc06169f32694de8", "snowflake": "19a1a80ffc06169f32694de8", @@ -19660,8 +19660,8 @@ "databricks": "26e762643db86e7646e57cba", "duckdb": "26e762643db86e7646e57cba", "generic": "26e762643db86e7646e57cba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "26e762643db86e7646e57cba", +"motherduck": "26e762643db86e7646e57cba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "26e762643db86e7646e57cba", "postgresql": "26e762643db86e7646e57cba", "snowflake": "26e762643db86e7646e57cba", @@ -19677,8 +19677,8 @@ "databricks": "078c0e4b25cdb89eb4ebd88f", "duckdb": "078c0e4b25cdb89eb4ebd88f", "generic": "078c0e4b25cdb89eb4ebd88f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "078c0e4b25cdb89eb4ebd88f", +"motherduck": "078c0e4b25cdb89eb4ebd88f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "078c0e4b25cdb89eb4ebd88f", "postgresql": "078c0e4b25cdb89eb4ebd88f", "snowflake": "6306b9e88f7b13dbb6a32c00", @@ -19690,14 +19690,14 @@ { "DuckDB": "7cd31289a5b99b5a5e24492d", "None": "7cd31289a5b99b5a5e24492d", -"bigquery": "7cd31289a5b99b5a5e24492d", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "7cd31289a5b99b5a5e24492d", "duckdb": "7cd31289a5b99b5a5e24492d", "generic": "7cd31289a5b99b5a5e24492d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7cd31289a5b99b5a5e24492d", -"postgres": "7cd31289a5b99b5a5e24492d", -"postgresql": "7cd31289a5b99b5a5e24492d", +"motherduck": "7cd31289a5b99b5a5e24492d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c5bdaf88df7ef3d9ae693106", +"postgresql": "c5bdaf88df7ef3d9ae693106", "snowflake": "7cd31289a5b99b5a5e24492d", "tsql": "7cd31289a5b99b5a5e24492d" } @@ -19711,8 +19711,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b7d6fc051a3c1ea6deb29ff4", "postgresql": "b7d6fc051a3c1ea6deb29ff4", "snowflake": "251aba3fef503ce64556c701", @@ -19728,8 +19728,8 @@ "databricks": "3eb44072dafa3bef75c97b6a", "duckdb": "3eb44072dafa3bef75c97b6a", "generic": "3eb44072dafa3bef75c97b6a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3eb44072dafa3bef75c97b6a", +"motherduck": "3eb44072dafa3bef75c97b6a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3eb44072dafa3bef75c97b6a", "postgresql": "3eb44072dafa3bef75c97b6a", "snowflake": "3eb44072dafa3bef75c97b6a", @@ -19745,8 +19745,8 @@ "databricks": "596dafb6039bd1951f03c6a3", "duckdb": "596dafb6039bd1951f03c6a3", "generic": "596dafb6039bd1951f03c6a3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "596dafb6039bd1951f03c6a3", +"motherduck": "596dafb6039bd1951f03c6a3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0d0c279c483ce8c07a9e7375", "postgresql": "0d0c279c483ce8c07a9e7375", "snowflake": "596dafb6039bd1951f03c6a3", @@ -19762,8 +19762,8 @@ "databricks": "b2c9b6ebca6c097f3caa0aa8", "duckdb": "b2c9b6ebca6c097f3caa0aa8", "generic": "b2c9b6ebca6c097f3caa0aa8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b2c9b6ebca6c097f3caa0aa8", +"motherduck": "b2c9b6ebca6c097f3caa0aa8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b2c9b6ebca6c097f3caa0aa8", "postgresql": "b2c9b6ebca6c097f3caa0aa8", "snowflake": "b2c9b6ebca6c097f3caa0aa8", @@ -19775,14 +19775,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -19796,8 +19796,8 @@ "databricks": "4d90ccac34ef123bb0b2a15c", "duckdb": "4d90ccac34ef123bb0b2a15c", "generic": "4d90ccac34ef123bb0b2a15c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4d90ccac34ef123bb0b2a15c", +"motherduck": "4d90ccac34ef123bb0b2a15c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4d90ccac34ef123bb0b2a15c", "postgresql": "4d90ccac34ef123bb0b2a15c", "snowflake": "4d90ccac34ef123bb0b2a15c", @@ -19813,8 +19813,8 @@ "databricks": "1ae302067c05dc9ae3a029f2", "duckdb": "1ae302067c05dc9ae3a029f2", "generic": "1ae302067c05dc9ae3a029f2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1ae302067c05dc9ae3a029f2", +"motherduck": "1ae302067c05dc9ae3a029f2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1ae302067c05dc9ae3a029f2", "postgresql": "1ae302067c05dc9ae3a029f2", "snowflake": "1ae302067c05dc9ae3a029f2", @@ -19830,8 +19830,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -19847,8 +19847,8 @@ "databricks": "b1ec979036647e3f2686cece", "duckdb": "b1ec979036647e3f2686cece", "generic": "b1ec979036647e3f2686cece", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b1ec979036647e3f2686cece", +"motherduck": "b1ec979036647e3f2686cece", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b1ec979036647e3f2686cece", "postgresql": "b1ec979036647e3f2686cece", "snowflake": "b1ec979036647e3f2686cece", @@ -19864,8 +19864,8 @@ "databricks": "962ea05bab624c1f8c259a65", "duckdb": "962ea05bab624c1f8c259a65", "generic": "962ea05bab624c1f8c259a65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "962ea05bab624c1f8c259a65", +"motherduck": "962ea05bab624c1f8c259a65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "962ea05bab624c1f8c259a65", "postgresql": "962ea05bab624c1f8c259a65", "snowflake": "962ea05bab624c1f8c259a65", @@ -19877,14 +19877,14 @@ { "DuckDB": "f319941cc6ec088f46ecbadb", "None": "f319941cc6ec088f46ecbadb", -"bigquery": "f319941cc6ec088f46ecbadb", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "f319941cc6ec088f46ecbadb", "duckdb": "f319941cc6ec088f46ecbadb", "generic": "f319941cc6ec088f46ecbadb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f319941cc6ec088f46ecbadb", -"postgres": "f319941cc6ec088f46ecbadb", -"postgresql": "f319941cc6ec088f46ecbadb", +"motherduck": "f319941cc6ec088f46ecbadb", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e664f46cfdd4441496e27023", +"postgresql": "e664f46cfdd4441496e27023", "snowflake": "f319941cc6ec088f46ecbadb", "tsql": "f319941cc6ec088f46ecbadb" } @@ -19898,8 +19898,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -19915,8 +19915,8 @@ "databricks": "717cf79ef777823eb3576deb", "duckdb": "717cf79ef777823eb3576deb", "generic": "717cf79ef777823eb3576deb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "717cf79ef777823eb3576deb", +"motherduck": "717cf79ef777823eb3576deb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "717cf79ef777823eb3576deb", "postgresql": "717cf79ef777823eb3576deb", "snowflake": "717cf79ef777823eb3576deb", @@ -19932,8 +19932,8 @@ "databricks": "8362a1ff79ce7c9350d11210", "duckdb": "8362a1ff79ce7c9350d11210", "generic": "8362a1ff79ce7c9350d11210", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8362a1ff79ce7c9350d11210", +"motherduck": "8362a1ff79ce7c9350d11210", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8362a1ff79ce7c9350d11210", "postgresql": "8362a1ff79ce7c9350d11210", "snowflake": "8362a1ff79ce7c9350d11210", @@ -19949,8 +19949,8 @@ "databricks": "688179272da645629077e980", "duckdb": "688179272da645629077e980", "generic": "688179272da645629077e980", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "688179272da645629077e980", +"motherduck": "688179272da645629077e980", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "688179272da645629077e980", "postgresql": "688179272da645629077e980", "snowflake": "b210f8eb26c37b422f786c6b", @@ -19966,8 +19966,8 @@ "databricks": "2e5b1ffe62d65d4d8c389ecd", "duckdb": "2e5b1ffe62d65d4d8c389ecd", "generic": "2e5b1ffe62d65d4d8c389ecd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2e5b1ffe62d65d4d8c389ecd", +"motherduck": "2e5b1ffe62d65d4d8c389ecd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2e5b1ffe62d65d4d8c389ecd", "postgresql": "2e5b1ffe62d65d4d8c389ecd", "snowflake": "2e5b1ffe62d65d4d8c389ecd", @@ -19983,8 +19983,8 @@ "databricks": "e6badc927d1adf4563b4f3a5", "duckdb": "e6badc927d1adf4563b4f3a5", "generic": "e6badc927d1adf4563b4f3a5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e6badc927d1adf4563b4f3a5", +"motherduck": "e6badc927d1adf4563b4f3a5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e6badc927d1adf4563b4f3a5", "postgresql": "e6badc927d1adf4563b4f3a5", "snowflake": "e6badc927d1adf4563b4f3a5", @@ -20000,8 +20000,8 @@ "databricks": "9070740b5c7f7fb5d091ad05", "duckdb": "9070740b5c7f7fb5d091ad05", "generic": "9070740b5c7f7fb5d091ad05", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9070740b5c7f7fb5d091ad05", +"motherduck": "9070740b5c7f7fb5d091ad05", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9070740b5c7f7fb5d091ad05", "postgresql": "9070740b5c7f7fb5d091ad05", "snowflake": "9070740b5c7f7fb5d091ad05", @@ -20014,11 +20014,11 @@ "DuckDB": "607f7c82658f39cce394b124", "None": "607f7c82658f39cce394b124", "bigquery": "607f7c82658f39cce394b124", -"databricks": "085a20ac29ebf425cac9e1d7", +"databricks": "607f7c82658f39cce394b124", "duckdb": "607f7c82658f39cce394b124", "generic": "607f7c82658f39cce394b124", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "085a20ac29ebf425cac9e1d7", +"motherduck": "607f7c82658f39cce394b124", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "607f7c82658f39cce394b124", "postgresql": "607f7c82658f39cce394b124", "snowflake": "607f7c82658f39cce394b124", @@ -20030,12 +20030,12 @@ { "DuckDB": "b3d92948d21d50dcbeb5f80b", "None": "b3d92948d21d50dcbeb5f80b", -"bigquery": "b3d92948d21d50dcbeb5f80b", +"bigquery": "e8b4e369659058a182f86446", "databricks": "b3d92948d21d50dcbeb5f80b", "duckdb": "b3d92948d21d50dcbeb5f80b", "generic": "b3d92948d21d50dcbeb5f80b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b3d92948d21d50dcbeb5f80b", +"motherduck": "b3d92948d21d50dcbeb5f80b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b3d92948d21d50dcbeb5f80b", "postgresql": "b3d92948d21d50dcbeb5f80b", "snowflake": "b3d92948d21d50dcbeb5f80b", @@ -20046,17 +20046,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -20068,8 +20068,8 @@ "databricks": "f4c43a87a78970bca1f88b09", "duckdb": "f4c43a87a78970bca1f88b09", "generic": "f4c43a87a78970bca1f88b09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f4c43a87a78970bca1f88b09", +"motherduck": "f4c43a87a78970bca1f88b09", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f4c43a87a78970bca1f88b09", "postgresql": "f4c43a87a78970bca1f88b09", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -20085,8 +20085,8 @@ "databricks": "2279ed9a279f48d8451671cf", "duckdb": "2279ed9a279f48d8451671cf", "generic": "2279ed9a279f48d8451671cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2279ed9a279f48d8451671cf", +"motherduck": "2279ed9a279f48d8451671cf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2279ed9a279f48d8451671cf", "postgresql": "2279ed9a279f48d8451671cf", "snowflake": "2279ed9a279f48d8451671cf", @@ -20098,14 +20098,14 @@ { "DuckDB": "881106a4a0d2de0ff7c56373", "None": "881106a4a0d2de0ff7c56373", -"bigquery": "881106a4a0d2de0ff7c56373", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "881106a4a0d2de0ff7c56373", "duckdb": "881106a4a0d2de0ff7c56373", "generic": "881106a4a0d2de0ff7c56373", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "881106a4a0d2de0ff7c56373", -"postgres": "881106a4a0d2de0ff7c56373", -"postgresql": "881106a4a0d2de0ff7c56373", +"motherduck": "881106a4a0d2de0ff7c56373", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cf167ec395fc843602e3b1e0", +"postgresql": "cf167ec395fc843602e3b1e0", "snowflake": "881106a4a0d2de0ff7c56373", "tsql": "881106a4a0d2de0ff7c56373" } @@ -20114,17 +20114,17 @@ "type", { "DuckDB": "f46cf4b4e131aa122b5b33d6", -"None": "f46cf4b4e131aa122b5b33d6", -"bigquery": "f46cf4b4e131aa122b5b33d6", -"databricks": "f46cf4b4e131aa122b5b33d6", +"None": "f774d5e8feadbf11edaa26b1", +"bigquery": "9bafb08d41fd1c208b5deca4", +"databricks": "f774d5e8feadbf11edaa26b1", "duckdb": "f46cf4b4e131aa122b5b33d6", -"generic": "f46cf4b4e131aa122b5b33d6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f46cf4b4e131aa122b5b33d6", +"generic": "f774d5e8feadbf11edaa26b1", +"motherduck": "f46cf4b4e131aa122b5b33d6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f46cf4b4e131aa122b5b33d6", "postgresql": "f46cf4b4e131aa122b5b33d6", "snowflake": "f46cf4b4e131aa122b5b33d6", -"tsql": "f46cf4b4e131aa122b5b33d6" +"tsql": "f774d5e8feadbf11edaa26b1" } ], [ @@ -20136,8 +20136,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -20153,8 +20153,8 @@ "databricks": "0475b57e9e9da81e87fa09ba", "duckdb": "0475b57e9e9da81e87fa09ba", "generic": "0475b57e9e9da81e87fa09ba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0475b57e9e9da81e87fa09ba", +"motherduck": "0475b57e9e9da81e87fa09ba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0475b57e9e9da81e87fa09ba", "postgresql": "0475b57e9e9da81e87fa09ba", "snowflake": "0475b57e9e9da81e87fa09ba", @@ -20170,8 +20170,8 @@ "databricks": "43e3e82e80c9c1580eb61a1d", "duckdb": "43e3e82e80c9c1580eb61a1d", "generic": "43e3e82e80c9c1580eb61a1d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "43e3e82e80c9c1580eb61a1d", +"motherduck": "43e3e82e80c9c1580eb61a1d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "43e3e82e80c9c1580eb61a1d", "postgresql": "43e3e82e80c9c1580eb61a1d", "snowflake": "43e3e82e80c9c1580eb61a1d", @@ -20187,8 +20187,8 @@ "databricks": "846f4ee64c34aab0cdabb9a0", "duckdb": "51b0e597909858f7f7aa4571", "generic": "51b0e597909858f7f7aa4571", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "51b0e597909858f7f7aa4571", +"motherduck": "51b0e597909858f7f7aa4571", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "51b0e597909858f7f7aa4571", "postgresql": "51b0e597909858f7f7aa4571", "snowflake": "4d94d72d93a870dcc1521bee", @@ -20204,8 +20204,8 @@ "databricks": "962ea05bab624c1f8c259a65", "duckdb": "962ea05bab624c1f8c259a65", "generic": "962ea05bab624c1f8c259a65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "962ea05bab624c1f8c259a65", +"motherduck": "962ea05bab624c1f8c259a65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "962ea05bab624c1f8c259a65", "postgresql": "962ea05bab624c1f8c259a65", "snowflake": "962ea05bab624c1f8c259a65", @@ -20221,8 +20221,8 @@ "databricks": "1275b5b0c52f69cf592c004b", "duckdb": "1275b5b0c52f69cf592c004b", "generic": "1275b5b0c52f69cf592c004b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1275b5b0c52f69cf592c004b", +"motherduck": "1275b5b0c52f69cf592c004b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1275b5b0c52f69cf592c004b", "postgresql": "1275b5b0c52f69cf592c004b", "snowflake": "1275b5b0c52f69cf592c004b", @@ -20238,8 +20238,8 @@ "databricks": "11d9e4471b50590a198e94a1", "duckdb": "a01889d0707cf6b364efb5f9", "generic": "94834ae92073b4606184619b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "11d9e4471b50590a198e94a1", +"motherduck": "a01889d0707cf6b364efb5f9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a01889d0707cf6b364efb5f9", "postgresql": "a01889d0707cf6b364efb5f9", "snowflake": "5bc09c1a33ccb4bd2e4075e5", @@ -20255,10 +20255,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -20267,15 +20267,15 @@ "type", { "DuckDB": "c43574432afc90bcba6944b2", -"None": "bf431bf304d57dd7b16797df", -"bigquery": "bf431bf304d57dd7b16797df", -"databricks": "bf431bf304d57dd7b16797df", +"None": "ba3896413716dbcd432e62dd", +"bigquery": "ba3896413716dbcd432e62dd", +"databricks": "ba3896413716dbcd432e62dd", "duckdb": "c43574432afc90bcba6944b2", -"generic": "bf431bf304d57dd7b16797df", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "b462303a5e5a209df5a75e4b", -"postgresql": "b462303a5e5a209df5a75e4b", +"generic": "ba3896413716dbcd432e62dd", +"motherduck": "c43574432afc90bcba6944b2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ba3896413716dbcd432e62dd", +"postgresql": "ba3896413716dbcd432e62dd", "snowflake": "26094c8570001998e247761e", "tsql": "ba3896413716dbcd432e62dd" } @@ -20289,8 +20289,8 @@ "databricks": "d90a78129f7050a540984f6f", "duckdb": "66f7ca02161349d637fa7d4e", "generic": "66f7ca02161349d637fa7d4e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d90a78129f7050a540984f6f", +"motherduck": "66f7ca02161349d637fa7d4e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "66f7ca02161349d637fa7d4e", "postgresql": "66f7ca02161349d637fa7d4e", "snowflake": "66f7ca02161349d637fa7d4e", @@ -20306,8 +20306,8 @@ "databricks": "83fdbd1f0a6ee4e72faa4d94", "duckdb": "83fdbd1f0a6ee4e72faa4d94", "generic": "83fdbd1f0a6ee4e72faa4d94", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "83fdbd1f0a6ee4e72faa4d94", +"motherduck": "83fdbd1f0a6ee4e72faa4d94", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "83fdbd1f0a6ee4e72faa4d94", "postgresql": "83fdbd1f0a6ee4e72faa4d94", "snowflake": "83fdbd1f0a6ee4e72faa4d94", @@ -20323,10 +20323,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "e2879cf4770e7f48d2dd704a", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "e2879cf4770e7f48d2dd704a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -20340,8 +20340,8 @@ "databricks": "7c8df74df226e73b459bfdba", "duckdb": "7c8df74df226e73b459bfdba", "generic": "7c8df74df226e73b459bfdba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7c8df74df226e73b459bfdba", +"motherduck": "7c8df74df226e73b459bfdba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7c8df74df226e73b459bfdba", "postgresql": "7c8df74df226e73b459bfdba", "snowflake": "7c8df74df226e73b459bfdba", @@ -20357,10 +20357,10 @@ "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "b0ce99394388f977f67fd1df", +"postgresql": "b0ce99394388f977f67fd1df", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -20370,14 +20370,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -20385,16 +20385,16 @@ [ "type", { -"DuckDB": "267fe8533e5d50f2cd3eb176", -"None": "8e0e269b33b63f52e24399e2", -"bigquery": "6a9a0937e74ae8ee83e705e4", -"databricks": "6a9a0937e74ae8ee83e705e4", -"duckdb": "267fe8533e5d50f2cd3eb176", -"generic": "8e0e269b33b63f52e24399e2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "43767924372e86845ec63bb6", -"postgresql": "43767924372e86845ec63bb6", +"DuckDB": "627ea26821da68299a37ddb8", +"None": "69ccced5b5ccc61253f2656b", +"bigquery": "69ccced5b5ccc61253f2656b", +"databricks": "69ccced5b5ccc61253f2656b", +"duckdb": "627ea26821da68299a37ddb8", +"generic": "69ccced5b5ccc61253f2656b", +"motherduck": "627ea26821da68299a37ddb8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "69ccced5b5ccc61253f2656b", +"postgresql": "69ccced5b5ccc61253f2656b", "snowflake": "5a7da03d64d9bf65f9984782", "tsql": "69ccced5b5ccc61253f2656b" } @@ -20404,14 +20404,14 @@ { "DuckDB": "4920de3b1203a0d66fc1b3cd", "None": "4920de3b1203a0d66fc1b3cd", -"bigquery": "4920de3b1203a0d66fc1b3cd", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "4920de3b1203a0d66fc1b3cd", "duckdb": "4920de3b1203a0d66fc1b3cd", "generic": "4920de3b1203a0d66fc1b3cd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4920de3b1203a0d66fc1b3cd", -"postgres": "4920de3b1203a0d66fc1b3cd", -"postgresql": "4920de3b1203a0d66fc1b3cd", +"motherduck": "4920de3b1203a0d66fc1b3cd", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8682e002a09d580d930440bf", +"postgresql": "8682e002a09d580d930440bf", "snowflake": "4920de3b1203a0d66fc1b3cd", "tsql": "4920de3b1203a0d66fc1b3cd" } @@ -20425,8 +20425,8 @@ "databricks": "7914189ff18b0e70ddf7d042", "duckdb": "7914189ff18b0e70ddf7d042", "generic": "7914189ff18b0e70ddf7d042", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7914189ff18b0e70ddf7d042", +"motherduck": "7914189ff18b0e70ddf7d042", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7914189ff18b0e70ddf7d042", "postgresql": "7914189ff18b0e70ddf7d042", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -20442,8 +20442,8 @@ "databricks": "c6889f2965db73017be6f6e7", "duckdb": "c6889f2965db73017be6f6e7", "generic": "c6889f2965db73017be6f6e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6889f2965db73017be6f6e7", +"motherduck": "c6889f2965db73017be6f6e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6889f2965db73017be6f6e7", "postgresql": "c6889f2965db73017be6f6e7", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -20455,14 +20455,14 @@ { "DuckDB": "4d0f0db1a550ed1e0da1a043", "None": "4d0f0db1a550ed1e0da1a043", -"bigquery": "4d0f0db1a550ed1e0da1a043", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "4d0f0db1a550ed1e0da1a043", "duckdb": "4d0f0db1a550ed1e0da1a043", "generic": "4d0f0db1a550ed1e0da1a043", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4d0f0db1a550ed1e0da1a043", -"postgres": "4d0f0db1a550ed1e0da1a043", -"postgresql": "4d0f0db1a550ed1e0da1a043", +"motherduck": "4d0f0db1a550ed1e0da1a043", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e47320d1030c166a05193bc4", +"postgresql": "e47320d1030c166a05193bc4", "snowflake": "4d0f0db1a550ed1e0da1a043", "tsql": "4d0f0db1a550ed1e0da1a043" } @@ -20476,8 +20476,8 @@ "databricks": "a9d9e03f8e981cec9dca7694", "duckdb": "a9d9e03f8e981cec9dca7694", "generic": "a9d9e03f8e981cec9dca7694", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a9d9e03f8e981cec9dca7694", +"motherduck": "a9d9e03f8e981cec9dca7694", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a9d9e03f8e981cec9dca7694", "postgresql": "a9d9e03f8e981cec9dca7694", "snowflake": "a9d9e03f8e981cec9dca7694", @@ -20493,8 +20493,8 @@ "databricks": "95961ec6c293c3d6b6bf65ec", "duckdb": "95961ec6c293c3d6b6bf65ec", "generic": "95961ec6c293c3d6b6bf65ec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "95961ec6c293c3d6b6bf65ec", +"motherduck": "95961ec6c293c3d6b6bf65ec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "95961ec6c293c3d6b6bf65ec", "postgresql": "95961ec6c293c3d6b6bf65ec", "snowflake": "95961ec6c293c3d6b6bf65ec", @@ -20510,8 +20510,8 @@ "databricks": "2c5e4442c23cf9e2b37caa7e", "duckdb": "2c5e4442c23cf9e2b37caa7e", "generic": "2c5e4442c23cf9e2b37caa7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c5e4442c23cf9e2b37caa7e", +"motherduck": "2c5e4442c23cf9e2b37caa7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2c5e4442c23cf9e2b37caa7e", "postgresql": "2c5e4442c23cf9e2b37caa7e", "snowflake": "2c5e4442c23cf9e2b37caa7e", @@ -20522,17 +20522,17 @@ "type", { "DuckDB": "a428302e4848ffe877284c84", -"None": "a428302e4848ffe877284c84", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "a428302e4848ffe877284c84", +"None": "c9b810639795a7bfab91b98d", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "c9b810639795a7bfab91b98d", "duckdb": "a428302e4848ffe877284c84", -"generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"generic": "c9b810639795a7bfab91b98d", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "a428302e4848ffe877284c84" +"tsql": "c9b810639795a7bfab91b98d" } ], [ @@ -20544,8 +20544,8 @@ "databricks": "1ebb508c9c012fda785eac26", "duckdb": "8702414b6372d27d90111c0d", "generic": "a7fcdf83ba95c5481cac4059", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1ebb508c9c012fda785eac26", +"motherduck": "8702414b6372d27d90111c0d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8702414b6372d27d90111c0d", "postgresql": "8702414b6372d27d90111c0d", "snowflake": "521d89d8efac986351afc368", @@ -20557,14 +20557,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -20578,8 +20578,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -20591,12 +20591,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -20612,8 +20612,8 @@ "databricks": "1bdf5908ca249b2331a0427c", "duckdb": "1bdf5908ca249b2331a0427c", "generic": "1bdf5908ca249b2331a0427c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1bdf5908ca249b2331a0427c", +"motherduck": "1bdf5908ca249b2331a0427c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1bdf5908ca249b2331a0427c", "postgresql": "1bdf5908ca249b2331a0427c", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -20625,12 +20625,12 @@ { "DuckDB": "dd061dac05d8a112fce62140", "None": "dd061dac05d8a112fce62140", -"bigquery": "dd061dac05d8a112fce62140", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "dd061dac05d8a112fce62140", "duckdb": "dd061dac05d8a112fce62140", "generic": "dd061dac05d8a112fce62140", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dd061dac05d8a112fce62140", +"motherduck": "dd061dac05d8a112fce62140", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "dd061dac05d8a112fce62140", "postgresql": "dd061dac05d8a112fce62140", "snowflake": "dd061dac05d8a112fce62140", @@ -20642,12 +20642,12 @@ { "DuckDB": "d7b4d15e2e3140069c23e6d4", "None": "d7b4d15e2e3140069c23e6d4", -"bigquery": "ba82290eb0d38f9638b8b0dc", +"bigquery": "01d1b160c1768fa1cd4b2e45", "databricks": "9ce61cdfd35b2a8b31faa983", "duckdb": "d7b4d15e2e3140069c23e6d4", "generic": "d7b4d15e2e3140069c23e6d4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d7b4d15e2e3140069c23e6d4", +"motherduck": "d7b4d15e2e3140069c23e6d4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d7b4d15e2e3140069c23e6d4", "postgresql": "d7b4d15e2e3140069c23e6d4", "snowflake": "906644f12c56f91f8767953d", @@ -20663,8 +20663,8 @@ "databricks": "cd932c5de0731b5ff7bddd24", "duckdb": "cd932c5de0731b5ff7bddd24", "generic": "cd932c5de0731b5ff7bddd24", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cd932c5de0731b5ff7bddd24", +"motherduck": "cd932c5de0731b5ff7bddd24", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cd932c5de0731b5ff7bddd24", "postgresql": "cd932c5de0731b5ff7bddd24", "snowflake": "cd932c5de0731b5ff7bddd24", @@ -20680,8 +20680,8 @@ "databricks": "104d3e046c47032c37f947b8", "duckdb": "104d3e046c47032c37f947b8", "generic": "104d3e046c47032c37f947b8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "104d3e046c47032c37f947b8", +"motherduck": "104d3e046c47032c37f947b8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "104d3e046c47032c37f947b8", "postgresql": "104d3e046c47032c37f947b8", "snowflake": "104d3e046c47032c37f947b8", @@ -20697,8 +20697,8 @@ "databricks": "f179445103bc721a2dc04002", "duckdb": "f179445103bc721a2dc04002", "generic": "f179445103bc721a2dc04002", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f179445103bc721a2dc04002", +"motherduck": "f179445103bc721a2dc04002", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f179445103bc721a2dc04002", "postgresql": "f179445103bc721a2dc04002", "snowflake": "f179445103bc721a2dc04002", @@ -20714,8 +20714,8 @@ "databricks": "7a924f1a13854aa3260f3f57", "duckdb": "7a924f1a13854aa3260f3f57", "generic": "7a924f1a13854aa3260f3f57", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7a924f1a13854aa3260f3f57", +"motherduck": "7a924f1a13854aa3260f3f57", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7a924f1a13854aa3260f3f57", "postgresql": "7a924f1a13854aa3260f3f57", "snowflake": "7a924f1a13854aa3260f3f57", @@ -20731,10 +20731,10 @@ "databricks": "3d4143f0295abc5ed5a3d8cc", "duckdb": "3d4143f0295abc5ed5a3d8cc", "generic": "3d4143f0295abc5ed5a3d8cc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3d4143f0295abc5ed5a3d8cc", -"postgres": "3d4143f0295abc5ed5a3d8cc", -"postgresql": "3d4143f0295abc5ed5a3d8cc", +"motherduck": "3d4143f0295abc5ed5a3d8cc", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "359568909836c6e577ab7e8c", +"postgresql": "359568909836c6e577ab7e8c", "snowflake": "3d4143f0295abc5ed5a3d8cc", "tsql": "3d4143f0295abc5ed5a3d8cc" } @@ -20748,8 +20748,8 @@ "databricks": "3f71ab5f3c1c040da96f2cec", "duckdb": "3f71ab5f3c1c040da96f2cec", "generic": "3f71ab5f3c1c040da96f2cec", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3f71ab5f3c1c040da96f2cec", +"motherduck": "3f71ab5f3c1c040da96f2cec", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3f71ab5f3c1c040da96f2cec", "postgresql": "3f71ab5f3c1c040da96f2cec", "snowflake": "3f71ab5f3c1c040da96f2cec", @@ -20765,8 +20765,8 @@ "databricks": "d4a375fcf7c3190951992469", "duckdb": "d4a375fcf7c3190951992469", "generic": "d4a375fcf7c3190951992469", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d4a375fcf7c3190951992469", +"motherduck": "d4a375fcf7c3190951992469", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d4a375fcf7c3190951992469", "postgresql": "d4a375fcf7c3190951992469", "snowflake": "6306b9e88f7b13dbb6a32c00", @@ -20782,8 +20782,8 @@ "databricks": "78709b3317abd96cc20340f7", "duckdb": "78709b3317abd96cc20340f7", "generic": "78709b3317abd96cc20340f7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "78709b3317abd96cc20340f7", +"motherduck": "78709b3317abd96cc20340f7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "78709b3317abd96cc20340f7", "postgresql": "78709b3317abd96cc20340f7", "snowflake": "78709b3317abd96cc20340f7", @@ -20799,8 +20799,8 @@ "databricks": "652ff8b21f638eac8fdc46c5", "duckdb": "652ff8b21f638eac8fdc46c5", "generic": "652ff8b21f638eac8fdc46c5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "652ff8b21f638eac8fdc46c5", +"motherduck": "652ff8b21f638eac8fdc46c5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "652ff8b21f638eac8fdc46c5", "postgresql": "652ff8b21f638eac8fdc46c5", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -20816,8 +20816,8 @@ "databricks": "293fdb322059805900912bee", "duckdb": "293fdb322059805900912bee", "generic": "293fdb322059805900912bee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "293fdb322059805900912bee", +"motherduck": "293fdb322059805900912bee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "293fdb322059805900912bee", "postgresql": "293fdb322059805900912bee", "snowflake": "293fdb322059805900912bee", @@ -20833,8 +20833,8 @@ "databricks": "3d23f811a03db519b598c815", "duckdb": "3d23f811a03db519b598c815", "generic": "3d23f811a03db519b598c815", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3d23f811a03db519b598c815", +"motherduck": "3d23f811a03db519b598c815", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3d23f811a03db519b598c815", "postgresql": "3d23f811a03db519b598c815", "snowflake": "3d23f811a03db519b598c815", @@ -20850,8 +20850,8 @@ "databricks": "1231fdcee1164056bb998fd8", "duckdb": "9cf8454127727005f919e6fc", "generic": "9a5c2bd43f94970e2d4d2210", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "84e2dbd148c2ae9480447652", +"motherduck": "9cf8454127727005f919e6fc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "eaa1040a087ac34088fbabf6", "postgresql": "eaa1040a087ac34088fbabf6", "snowflake": "143732e0372eaf73cb82cfe7", @@ -20867,8 +20867,8 @@ "databricks": "2166772bfafca596e1de9d10", "duckdb": "2166772bfafca596e1de9d10", "generic": "2166772bfafca596e1de9d10", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2166772bfafca596e1de9d10", +"motherduck": "2166772bfafca596e1de9d10", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2166772bfafca596e1de9d10", "postgresql": "2166772bfafca596e1de9d10", "snowflake": "2166772bfafca596e1de9d10", @@ -20884,8 +20884,8 @@ "databricks": "8441d00030df06ad6a5534d3", "duckdb": "8441d00030df06ad6a5534d3", "generic": "8441d00030df06ad6a5534d3", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8441d00030df06ad6a5534d3", +"motherduck": "8441d00030df06ad6a5534d3", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8441d00030df06ad6a5534d3", "postgresql": "8441d00030df06ad6a5534d3", "snowflake": "97a20344d811fb77c7690863", @@ -20901,8 +20901,8 @@ "databricks": "013a8446f043affffcf194f8", "duckdb": "013a8446f043affffcf194f8", "generic": "013a8446f043affffcf194f8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "013a8446f043affffcf194f8", +"motherduck": "013a8446f043affffcf194f8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "013a8446f043affffcf194f8", "postgresql": "013a8446f043affffcf194f8", "snowflake": "97a20344d811fb77c7690863", @@ -20914,12 +20914,12 @@ { "DuckDB": "461923a0d34defd0c726d6e5", "None": "461923a0d34defd0c726d6e5", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "461923a0d34defd0c726d6e5", "duckdb": "461923a0d34defd0c726d6e5", "generic": "461923a0d34defd0c726d6e5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "461923a0d34defd0c726d6e5", +"motherduck": "461923a0d34defd0c726d6e5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "461923a0d34defd0c726d6e5", "postgresql": "461923a0d34defd0c726d6e5", "snowflake": "461923a0d34defd0c726d6e5", @@ -20935,8 +20935,8 @@ "databricks": "dcbfb83ead5b0e32e500635b", "duckdb": "dcbfb83ead5b0e32e500635b", "generic": "dcbfb83ead5b0e32e500635b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dcbfb83ead5b0e32e500635b", +"motherduck": "dcbfb83ead5b0e32e500635b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "dcbfb83ead5b0e32e500635b", "postgresql": "dcbfb83ead5b0e32e500635b", "snowflake": "dcbfb83ead5b0e32e500635b", @@ -20952,10 +20952,10 @@ "databricks": "a686bb6db17ab079cfc79224", "duckdb": "a686bb6db17ab079cfc79224", "generic": "a686bb6db17ab079cfc79224", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a686bb6db17ab079cfc79224", -"postgres": "a686bb6db17ab079cfc79224", -"postgresql": "a686bb6db17ab079cfc79224", +"motherduck": "a686bb6db17ab079cfc79224", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "74a30f6aa9def0b58afc616c", +"postgresql": "74a30f6aa9def0b58afc616c", "snowflake": "1096c3c8687c8e8eb538ff74", "tsql": "a686bb6db17ab079cfc79224" } @@ -20969,10 +20969,10 @@ "databricks": "e2879cf4770e7f48d2dd704a", "duckdb": "9d006c5299feb04426ce31ac", "generic": "e2879cf4770e7f48d2dd704a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2879cf4770e7f48d2dd704a", -"postgres": "e2879cf4770e7f48d2dd704a", -"postgresql": "e2879cf4770e7f48d2dd704a", +"motherduck": "9d006c5299feb04426ce31ac", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2d4aea8dec5521e258596f3d", +"postgresql": "2d4aea8dec5521e258596f3d", "snowflake": "97a20344d811fb77c7690863", "tsql": "e2879cf4770e7f48d2dd704a" } @@ -20982,14 +20982,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -21003,8 +21003,8 @@ "databricks": "013a8446f043affffcf194f8", "duckdb": "013a8446f043affffcf194f8", "generic": "013a8446f043affffcf194f8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "013a8446f043affffcf194f8", +"motherduck": "013a8446f043affffcf194f8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "013a8446f043affffcf194f8", "postgresql": "013a8446f043affffcf194f8", "snowflake": "97a20344d811fb77c7690863", @@ -21015,17 +21015,17 @@ "type", { "DuckDB": "a428302e4848ffe877284c84", -"None": "a428302e4848ffe877284c84", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "a428302e4848ffe877284c84", +"None": "c9b810639795a7bfab91b98d", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "c9b810639795a7bfab91b98d", "duckdb": "a428302e4848ffe877284c84", -"generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"generic": "c9b810639795a7bfab91b98d", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "a428302e4848ffe877284c84" +"tsql": "c9b810639795a7bfab91b98d" } ], [ @@ -21037,8 +21037,8 @@ "databricks": "8790273499dae4e6cbd2d034", "duckdb": "8790273499dae4e6cbd2d034", "generic": "8790273499dae4e6cbd2d034", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8790273499dae4e6cbd2d034", +"motherduck": "8790273499dae4e6cbd2d034", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8790273499dae4e6cbd2d034", "postgresql": "8790273499dae4e6cbd2d034", "snowflake": "9aff7fcc7d550e1a8d105471", @@ -21054,10 +21054,10 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "19e127c23b4598727a2d8ceb", "generic": "19e127c23b4598727a2d8ceb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aed0419e7f2ce7c0a8892248", -"postgres": "19e127c23b4598727a2d8ceb", -"postgresql": "19e127c23b4598727a2d8ceb", +"motherduck": "19e127c23b4598727a2d8ceb", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "111af35848fe500ec2ae54b2", +"postgresql": "111af35848fe500ec2ae54b2", "snowflake": "aed0419e7f2ce7c0a8892248", "tsql": "19e127c23b4598727a2d8ceb" } @@ -21071,8 +21071,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "b8321619b794db091c9e96a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b8321619b794db091c9e96a1", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b8321619b794db091c9e96a1", "postgresql": "b8321619b794db091c9e96a1", "snowflake": "b8321619b794db091c9e96a1", @@ -21088,10 +21088,10 @@ "databricks": "bce60580ef3a1dc4ce569fff", "duckdb": "bce60580ef3a1dc4ce569fff", "generic": "bce60580ef3a1dc4ce569fff", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bce60580ef3a1dc4ce569fff", -"postgres": "bce60580ef3a1dc4ce569fff", -"postgresql": "bce60580ef3a1dc4ce569fff", +"motherduck": "bce60580ef3a1dc4ce569fff", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eee236ea1427bdddbaf088ac", +"postgresql": "eee236ea1427bdddbaf088ac", "snowflake": "bce60580ef3a1dc4ce569fff", "tsql": "bce60580ef3a1dc4ce569fff" } @@ -21105,8 +21105,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "305aaf0b95cee629dd46380c", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "305aaf0b95cee629dd46380c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -21122,8 +21122,8 @@ "databricks": "73ed909ac0c80ba48ae6d17c", "duckdb": "73ed909ac0c80ba48ae6d17c", "generic": "73ed909ac0c80ba48ae6d17c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73ed909ac0c80ba48ae6d17c", +"motherduck": "73ed909ac0c80ba48ae6d17c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "73ed909ac0c80ba48ae6d17c", "postgresql": "73ed909ac0c80ba48ae6d17c", "snowflake": "73ed909ac0c80ba48ae6d17c", @@ -21133,14 +21133,14 @@ [ "type", { -"DuckDB": "c6654395c62d21b415299f72", +"DuckDB": "0712c33a016f7dd6a37ee011", "None": "c6654395c62d21b415299f72", "bigquery": "0712c33a016f7dd6a37ee011", "databricks": "c6654395c62d21b415299f72", -"duckdb": "c6654395c62d21b415299f72", +"duckdb": "0712c33a016f7dd6a37ee011", "generic": "c6654395c62d21b415299f72", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2978a8fe925c7f93c6e9fcfb", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6654395c62d21b415299f72", "postgresql": "c6654395c62d21b415299f72", "snowflake": "0712c33a016f7dd6a37ee011", @@ -21156,8 +21156,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -21173,8 +21173,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -21190,8 +21190,8 @@ "databricks": "53100c87dc0e46dc31d2c6cc", "duckdb": "53100c87dc0e46dc31d2c6cc", "generic": "53100c87dc0e46dc31d2c6cc", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "53100c87dc0e46dc31d2c6cc", +"motherduck": "53100c87dc0e46dc31d2c6cc", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "53100c87dc0e46dc31d2c6cc", "postgresql": "53100c87dc0e46dc31d2c6cc", "snowflake": "53100c87dc0e46dc31d2c6cc", @@ -21207,8 +21207,8 @@ "databricks": "6992a9c8899f9fe5de26869c", "duckdb": "081a4ec9ca8c1ffbb8c41e4d", "generic": "6992a9c8899f9fe5de26869c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "081a4ec9ca8c1ffbb8c41e4d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "081a4ec9ca8c1ffbb8c41e4d", "postgresql": "081a4ec9ca8c1ffbb8c41e4d", "snowflake": "808e51c2deac58e29ecb851e", @@ -21224,8 +21224,8 @@ "databricks": "0c4610f182f18bf41d306e01", "duckdb": "0c4610f182f18bf41d306e01", "generic": "0c4610f182f18bf41d306e01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0c4610f182f18bf41d306e01", +"motherduck": "0c4610f182f18bf41d306e01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0c4610f182f18bf41d306e01", "postgresql": "0c4610f182f18bf41d306e01", "snowflake": "0c4610f182f18bf41d306e01", @@ -21241,8 +21241,8 @@ "databricks": "e068bc199d71601c823bb254", "duckdb": "e068bc199d71601c823bb254", "generic": "e068bc199d71601c823bb254", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e068bc199d71601c823bb254", +"motherduck": "e068bc199d71601c823bb254", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e068bc199d71601c823bb254", "postgresql": "e068bc199d71601c823bb254", "snowflake": "e068bc199d71601c823bb254", @@ -21258,10 +21258,10 @@ "databricks": "18036ada4276b0930d870788", "duckdb": "18036ada4276b0930d870788", "generic": "18036ada4276b0930d870788", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18036ada4276b0930d870788", -"postgres": "18036ada4276b0930d870788", -"postgresql": "18036ada4276b0930d870788", +"motherduck": "18036ada4276b0930d870788", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "899d81b12a6c3aed6cf9dd53", +"postgresql": "899d81b12a6c3aed6cf9dd53", "snowflake": "18036ada4276b0930d870788", "tsql": "18036ada4276b0930d870788" } @@ -21275,8 +21275,8 @@ "databricks": "360298d6139a1e6f476862de", "duckdb": "3b86998e2968bafafb02d6c0", "generic": "2f6a1345f274f18462c576a9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "3b86998e2968bafafb02d6c0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3b86998e2968bafafb02d6c0", "postgresql": "3b86998e2968bafafb02d6c0", "snowflake": "f5272904633fd6945c70dc00", @@ -21292,8 +21292,8 @@ "databricks": "c6889f2965db73017be6f6e7", "duckdb": "c6889f2965db73017be6f6e7", "generic": "c6889f2965db73017be6f6e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c6889f2965db73017be6f6e7", +"motherduck": "c6889f2965db73017be6f6e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c6889f2965db73017be6f6e7", "postgresql": "c6889f2965db73017be6f6e7", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -21309,8 +21309,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -21326,8 +21326,8 @@ "databricks": "93fcb7d3c2979f1531daccf5", "duckdb": "93fcb7d3c2979f1531daccf5", "generic": "93fcb7d3c2979f1531daccf5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "93fcb7d3c2979f1531daccf5", +"motherduck": "93fcb7d3c2979f1531daccf5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "93fcb7d3c2979f1531daccf5", "postgresql": "93fcb7d3c2979f1531daccf5", "snowflake": "93fcb7d3c2979f1531daccf5", @@ -21343,8 +21343,8 @@ "databricks": "0475b57e9e9da81e87fa09ba", "duckdb": "0475b57e9e9da81e87fa09ba", "generic": "0475b57e9e9da81e87fa09ba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0475b57e9e9da81e87fa09ba", +"motherduck": "0475b57e9e9da81e87fa09ba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0475b57e9e9da81e87fa09ba", "postgresql": "0475b57e9e9da81e87fa09ba", "snowflake": "0475b57e9e9da81e87fa09ba", @@ -21360,8 +21360,8 @@ "databricks": "f35059f8984f0ac12ad1dc75", "duckdb": "f35059f8984f0ac12ad1dc75", "generic": "f35059f8984f0ac12ad1dc75", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f35059f8984f0ac12ad1dc75", +"motherduck": "f35059f8984f0ac12ad1dc75", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f35059f8984f0ac12ad1dc75", "postgresql": "f35059f8984f0ac12ad1dc75", "snowflake": "f35059f8984f0ac12ad1dc75", @@ -21377,8 +21377,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -21394,8 +21394,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -21411,8 +21411,8 @@ "databricks": "f43dac38f34a1c0618a3f812", "duckdb": "f43dac38f34a1c0618a3f812", "generic": "f43dac38f34a1c0618a3f812", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f43dac38f34a1c0618a3f812", +"motherduck": "f43dac38f34a1c0618a3f812", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f43dac38f34a1c0618a3f812", "postgresql": "f43dac38f34a1c0618a3f812", "snowflake": "f43dac38f34a1c0618a3f812", @@ -21428,8 +21428,8 @@ "databricks": "42a76d5ff05d0f91911ff368", "duckdb": "42a76d5ff05d0f91911ff368", "generic": "42a76d5ff05d0f91911ff368", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "42a76d5ff05d0f91911ff368", +"motherduck": "42a76d5ff05d0f91911ff368", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "42a76d5ff05d0f91911ff368", "postgresql": "42a76d5ff05d0f91911ff368", "snowflake": "42a76d5ff05d0f91911ff368", @@ -21441,14 +21441,14 @@ { "DuckDB": "7cd31289a5b99b5a5e24492d", "None": "7cd31289a5b99b5a5e24492d", -"bigquery": "7cd31289a5b99b5a5e24492d", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "7cd31289a5b99b5a5e24492d", "duckdb": "7cd31289a5b99b5a5e24492d", "generic": "7cd31289a5b99b5a5e24492d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7cd31289a5b99b5a5e24492d", -"postgres": "7cd31289a5b99b5a5e24492d", -"postgresql": "7cd31289a5b99b5a5e24492d", +"motherduck": "7cd31289a5b99b5a5e24492d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c5bdaf88df7ef3d9ae693106", +"postgresql": "c5bdaf88df7ef3d9ae693106", "snowflake": "7cd31289a5b99b5a5e24492d", "tsql": "7cd31289a5b99b5a5e24492d" } @@ -21458,12 +21458,12 @@ { "DuckDB": "cbd04e906c540e91d7c8ea88", "None": "cbd04e906c540e91d7c8ea88", -"bigquery": "b5c4d5044b2a6e70633f0d0a", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "cbd04e906c540e91d7c8ea88", "duckdb": "cbd04e906c540e91d7c8ea88", "generic": "cbd04e906c540e91d7c8ea88", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cbd04e906c540e91d7c8ea88", +"motherduck": "cbd04e906c540e91d7c8ea88", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cbd04e906c540e91d7c8ea88", "postgresql": "cbd04e906c540e91d7c8ea88", "snowflake": "cbd04e906c540e91d7c8ea88", @@ -21479,8 +21479,8 @@ "databricks": "7177019d5faf5b3046623257", "duckdb": "7177019d5faf5b3046623257", "generic": "7177019d5faf5b3046623257", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7177019d5faf5b3046623257", +"motherduck": "7177019d5faf5b3046623257", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7177019d5faf5b3046623257", "postgresql": "7177019d5faf5b3046623257", "snowflake": "7177019d5faf5b3046623257", @@ -21496,8 +21496,8 @@ "databricks": "277693b7c19b2696f01403ba", "duckdb": "277693b7c19b2696f01403ba", "generic": "277693b7c19b2696f01403ba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "277693b7c19b2696f01403ba", +"motherduck": "277693b7c19b2696f01403ba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "277693b7c19b2696f01403ba", "postgresql": "277693b7c19b2696f01403ba", "snowflake": "277693b7c19b2696f01403ba", @@ -21513,8 +21513,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -21530,8 +21530,8 @@ "databricks": "1bdf5908ca249b2331a0427c", "duckdb": "1bdf5908ca249b2331a0427c", "generic": "1bdf5908ca249b2331a0427c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1bdf5908ca249b2331a0427c", +"motherduck": "1bdf5908ca249b2331a0427c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1bdf5908ca249b2331a0427c", "postgresql": "1bdf5908ca249b2331a0427c", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -21547,8 +21547,8 @@ "databricks": "e1c74d96ac578f9323bf1bbd", "duckdb": "e1c74d96ac578f9323bf1bbd", "generic": "e1c74d96ac578f9323bf1bbd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e1c74d96ac578f9323bf1bbd", +"motherduck": "e1c74d96ac578f9323bf1bbd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e1c74d96ac578f9323bf1bbd", "postgresql": "e1c74d96ac578f9323bf1bbd", "snowflake": "e1c74d96ac578f9323bf1bbd", @@ -21564,8 +21564,8 @@ "databricks": "cafb5cff5526dd419baad0ed", "duckdb": "cafb5cff5526dd419baad0ed", "generic": "cafb5cff5526dd419baad0ed", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cafb5cff5526dd419baad0ed", +"motherduck": "cafb5cff5526dd419baad0ed", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cafb5cff5526dd419baad0ed", "postgresql": "cafb5cff5526dd419baad0ed", "snowflake": "cafb5cff5526dd419baad0ed", @@ -21581,8 +21581,8 @@ "databricks": "e8b6e0bd2ded7b9eec6209e4", "duckdb": "e8b6e0bd2ded7b9eec6209e4", "generic": "e8b6e0bd2ded7b9eec6209e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8b6e0bd2ded7b9eec6209e4", +"motherduck": "e8b6e0bd2ded7b9eec6209e4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8b6e0bd2ded7b9eec6209e4", "postgresql": "e8b6e0bd2ded7b9eec6209e4", "snowflake": "e8b6e0bd2ded7b9eec6209e4", @@ -21598,8 +21598,8 @@ "databricks": "b2c9b6ebca6c097f3caa0aa8", "duckdb": "b2c9b6ebca6c097f3caa0aa8", "generic": "b2c9b6ebca6c097f3caa0aa8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b2c9b6ebca6c097f3caa0aa8", +"motherduck": "b2c9b6ebca6c097f3caa0aa8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b2c9b6ebca6c097f3caa0aa8", "postgresql": "b2c9b6ebca6c097f3caa0aa8", "snowflake": "b2c9b6ebca6c097f3caa0aa8", @@ -21611,14 +21611,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -21628,12 +21628,12 @@ { "DuckDB": "18db5afe2c1e80d1ee652b54", "None": "18db5afe2c1e80d1ee652b54", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "18db5afe2c1e80d1ee652b54", "postgresql": "18db5afe2c1e80d1ee652b54", "snowflake": "18db5afe2c1e80d1ee652b54", @@ -21649,8 +21649,8 @@ "databricks": "5524945ebcb15c363d317b3c", "duckdb": "219314eaf94cabfe1c7e559c", "generic": "4aab4bdd20039d5e16058b1b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5524945ebcb15c363d317b3c", +"motherduck": "219314eaf94cabfe1c7e559c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "219314eaf94cabfe1c7e559c", "postgresql": "219314eaf94cabfe1c7e559c", "snowflake": "b767a0334d4c0090a22665f5", @@ -21666,8 +21666,8 @@ "databricks": "84e86bec7646b6ad19ceef59", "duckdb": "6a2162b5abe5a73734c47041", "generic": "6a2162b5abe5a73734c47041", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6a2162b5abe5a73734c47041", +"motherduck": "6a2162b5abe5a73734c47041", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6a2162b5abe5a73734c47041", "postgresql": "6a2162b5abe5a73734c47041", "snowflake": "16d902e899040d0c0c9dcf58", @@ -21683,8 +21683,8 @@ "databricks": "8790273499dae4e6cbd2d034", "duckdb": "8790273499dae4e6cbd2d034", "generic": "8790273499dae4e6cbd2d034", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8790273499dae4e6cbd2d034", +"motherduck": "8790273499dae4e6cbd2d034", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8790273499dae4e6cbd2d034", "postgresql": "8790273499dae4e6cbd2d034", "snowflake": "9aff7fcc7d550e1a8d105471", @@ -21700,10 +21700,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -21717,8 +21717,8 @@ "databricks": "77298ddff3580b41e361ba6d", "duckdb": "bf74994a132713cd5a93b70b", "generic": "74d4939af66c472f17ecfa08", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ce873d09a353281e316461aa", +"motherduck": "bf74994a132713cd5a93b70b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cf182821f911fadedb1f868d", "postgresql": "cf182821f911fadedb1f868d", "snowflake": "5c5b258591d9f7cb305a4220", @@ -21734,8 +21734,8 @@ "databricks": "b66ef401bfc62ea5f67697f0", "duckdb": "b66ef401bfc62ea5f67697f0", "generic": "b66ef401bfc62ea5f67697f0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b66ef401bfc62ea5f67697f0", +"motherduck": "b66ef401bfc62ea5f67697f0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b66ef401bfc62ea5f67697f0", "postgresql": "b66ef401bfc62ea5f67697f0", "snowflake": "b66ef401bfc62ea5f67697f0", @@ -21747,12 +21747,12 @@ { "DuckDB": "8203bd5901a8ec71ff7c103e", "None": "8203bd5901a8ec71ff7c103e", -"bigquery": "2a0918be53051fe629d9ff8f", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "8203bd5901a8ec71ff7c103e", "duckdb": "8203bd5901a8ec71ff7c103e", "generic": "8203bd5901a8ec71ff7c103e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8203bd5901a8ec71ff7c103e", +"motherduck": "8203bd5901a8ec71ff7c103e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8203bd5901a8ec71ff7c103e", "postgresql": "8203bd5901a8ec71ff7c103e", "snowflake": "8203bd5901a8ec71ff7c103e", @@ -21764,14 +21764,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -21781,12 +21781,12 @@ { "DuckDB": "6199046833cb1b5de6e50c1f", "None": "6199046833cb1b5de6e50c1f", -"bigquery": "6199046833cb1b5de6e50c1f", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "6199046833cb1b5de6e50c1f", "duckdb": "6199046833cb1b5de6e50c1f", "generic": "6199046833cb1b5de6e50c1f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6199046833cb1b5de6e50c1f", +"motherduck": "6199046833cb1b5de6e50c1f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6199046833cb1b5de6e50c1f", "postgresql": "6199046833cb1b5de6e50c1f", "snowflake": "6199046833cb1b5de6e50c1f", @@ -21798,12 +21798,12 @@ { "DuckDB": "f1158467f7ab7b47d7e993d0", "None": "f1158467f7ab7b47d7e993d0", -"bigquery": "f1158467f7ab7b47d7e993d0", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "f1158467f7ab7b47d7e993d0", "duckdb": "f1158467f7ab7b47d7e993d0", "generic": "f1158467f7ab7b47d7e993d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f1158467f7ab7b47d7e993d0", +"motherduck": "f1158467f7ab7b47d7e993d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f1158467f7ab7b47d7e993d0", "postgresql": "f1158467f7ab7b47d7e993d0", "snowflake": "f1158467f7ab7b47d7e993d0", @@ -21814,17 +21814,17 @@ "type", { "DuckDB": "23f70cb2333740d79361d52e", -"None": "23f70cb2333740d79361d52e", -"bigquery": "816384049477221b0a4a5b89", -"databricks": "23f70cb2333740d79361d52e", +"None": "a33d60d9b009878ab623c1d8", +"bigquery": "bb92c01c64d1a2d32abb67b5", +"databricks": "a33d60d9b009878ab623c1d8", "duckdb": "23f70cb2333740d79361d52e", -"generic": "23f70cb2333740d79361d52e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "23f70cb2333740d79361d52e", -"postgres": "23f70cb2333740d79361d52e", -"postgresql": "23f70cb2333740d79361d52e", +"generic": "a33d60d9b009878ab623c1d8", +"motherduck": "23f70cb2333740d79361d52e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f276e907770196606577c932", +"postgresql": "f276e907770196606577c932", "snowflake": "23f70cb2333740d79361d52e", -"tsql": "23f70cb2333740d79361d52e" +"tsql": "a33d60d9b009878ab623c1d8" } ], [ @@ -21836,8 +21836,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -21853,10 +21853,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -21870,8 +21870,8 @@ "databricks": "527932781ba4bab32cb5e881", "duckdb": "527932781ba4bab32cb5e881", "generic": "527932781ba4bab32cb5e881", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "527932781ba4bab32cb5e881", +"motherduck": "527932781ba4bab32cb5e881", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "527932781ba4bab32cb5e881", "postgresql": "527932781ba4bab32cb5e881", "snowflake": "527932781ba4bab32cb5e881", @@ -21883,12 +21883,12 @@ { "DuckDB": "623dbde002b57284f477b0b5", "None": "623dbde002b57284f477b0b5", -"bigquery": "623dbde002b57284f477b0b5", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "623dbde002b57284f477b0b5", "duckdb": "623dbde002b57284f477b0b5", "generic": "623dbde002b57284f477b0b5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "623dbde002b57284f477b0b5", +"motherduck": "623dbde002b57284f477b0b5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "623dbde002b57284f477b0b5", "postgresql": "623dbde002b57284f477b0b5", "snowflake": "623dbde002b57284f477b0b5", @@ -21904,8 +21904,8 @@ "databricks": "a12a2ad8c11bbf6a41794d33", "duckdb": "a12a2ad8c11bbf6a41794d33", "generic": "a12a2ad8c11bbf6a41794d33", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a12a2ad8c11bbf6a41794d33", +"motherduck": "a12a2ad8c11bbf6a41794d33", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a12a2ad8c11bbf6a41794d33", "postgresql": "a12a2ad8c11bbf6a41794d33", "snowflake": "a12a2ad8c11bbf6a41794d33", @@ -21917,12 +21917,12 @@ { "DuckDB": "558da38ec4d9d5d6c72f60cf", "None": "558da38ec4d9d5d6c72f60cf", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "558da38ec4d9d5d6c72f60cf", "generic": "558da38ec4d9d5d6c72f60cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "558da38ec4d9d5d6c72f60cf", +"motherduck": "558da38ec4d9d5d6c72f60cf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "558da38ec4d9d5d6c72f60cf", "postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "558da38ec4d9d5d6c72f60cf", @@ -21933,17 +21933,17 @@ "type", { "DuckDB": "363159f251e264604338e690", -"None": "363159f251e264604338e690", -"bigquery": "363159f251e264604338e690", -"databricks": "363159f251e264604338e690", +"None": "9a63ad6168418af23d9b6fd1", +"bigquery": "8b8d5f75ba6dec68b4cebc6b", +"databricks": "9a63ad6168418af23d9b6fd1", "duckdb": "363159f251e264604338e690", -"generic": "363159f251e264604338e690", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "363159f251e264604338e690", -"postgres": "363159f251e264604338e690", -"postgresql": "363159f251e264604338e690", +"generic": "9a63ad6168418af23d9b6fd1", +"motherduck": "363159f251e264604338e690", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f276e907770196606577c932", +"postgresql": "f276e907770196606577c932", "snowflake": "363159f251e264604338e690", -"tsql": "363159f251e264604338e690" +"tsql": "9a63ad6168418af23d9b6fd1" } ], [ @@ -21952,11 +21952,11 @@ "DuckDB": "b6200c25bc3749f5724d237c", "None": "b6200c25bc3749f5724d237c", "bigquery": "b6200c25bc3749f5724d237c", -"databricks": "6b1db1e2f94caeb40dbea19b", +"databricks": "b6200c25bc3749f5724d237c", "duckdb": "b6200c25bc3749f5724d237c", "generic": "b6200c25bc3749f5724d237c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "b6200c25bc3749f5724d237c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b6200c25bc3749f5724d237c", "postgresql": "b6200c25bc3749f5724d237c", "snowflake": "b6200c25bc3749f5724d237c", @@ -21972,8 +21972,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -21989,8 +21989,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -22001,32 +22001,32 @@ "type", { "DuckDB": "129cd0245509c52e0d7ac815", -"None": "8d1119d9033bc544ced02739", -"bigquery": "ef3ab21b1ec5acc43562f6e2", -"databricks": "5b41dbff68938ce2e8a5c35d", +"None": "1071df52a9e8350ac0c2cd33", +"bigquery": "1071df52a9e8350ac0c2cd33", +"databricks": "1071df52a9e8350ac0c2cd33", "duckdb": "129cd0245509c52e0d7ac815", -"generic": "8d1119d9033bc544ced02739", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b9f1a78bcda3bc968346cc10", -"postgres": "129cd0245509c52e0d7ac815", -"postgresql": "129cd0245509c52e0d7ac815", +"generic": "1071df52a9e8350ac0c2cd33", +"motherduck": "129cd0245509c52e0d7ac815", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "1071df52a9e8350ac0c2cd33", +"postgresql": "1071df52a9e8350ac0c2cd33", "snowflake": "24b68111659c8d395da7df6a", -"tsql": "e42dc1c3607e8e3b3c865c3f" +"tsql": "1071df52a9e8350ac0c2cd33" } ], [ "type", { -"DuckDB": "60bd7daa04bed898e52118ab", -"None": "070c0836fb3402c924b00b0e", +"DuckDB": "3d2b3b1097973d173f205074", +"None": "3aac0f59706540c7a59cbce4", "bigquery": "3aac0f59706540c7a59cbce4", -"databricks": "070c0836fb3402c924b00b0e", -"duckdb": "60bd7daa04bed898e52118ab", -"generic": "070c0836fb3402c924b00b0e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "60bd7daa04bed898e52118ab", -"postgresql": "60bd7daa04bed898e52118ab", +"databricks": "3aac0f59706540c7a59cbce4", +"duckdb": "3d2b3b1097973d173f205074", +"generic": "3aac0f59706540c7a59cbce4", +"motherduck": "3d2b3b1097973d173f205074", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3d2b3b1097973d173f205074", +"postgresql": "3d2b3b1097973d173f205074", "snowflake": "0a5fcaec13e26e3caacd5345", "tsql": "33f30462ac4f41af4b28bd1a" } @@ -22040,8 +22040,8 @@ "databricks": "94006e4eb73bd1f728de7a83", "duckdb": "2f6203391b0efaaf0a3c9d00", "generic": "2f6203391b0efaaf0a3c9d00", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2f6203391b0efaaf0a3c9d00", +"motherduck": "2f6203391b0efaaf0a3c9d00", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2f6203391b0efaaf0a3c9d00", "postgresql": "2f6203391b0efaaf0a3c9d00", "snowflake": "dc61037c72b2e7b4730d26cc", @@ -22057,8 +22057,8 @@ "databricks": "6a534f7a641457b40e35d984", "duckdb": "6a534f7a641457b40e35d984", "generic": "6a534f7a641457b40e35d984", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6a534f7a641457b40e35d984", +"motherduck": "6a534f7a641457b40e35d984", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6a534f7a641457b40e35d984", "postgresql": "6a534f7a641457b40e35d984", "snowflake": "6a534f7a641457b40e35d984", @@ -22070,14 +22070,14 @@ { "DuckDB": "60b39a68a18438603dd5496d", "None": "60b39a68a18438603dd5496d", -"bigquery": "60b39a68a18438603dd5496d", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "60b39a68a18438603dd5496d", "duckdb": "60b39a68a18438603dd5496d", "generic": "60b39a68a18438603dd5496d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "60b39a68a18438603dd5496d", -"postgres": "60b39a68a18438603dd5496d", -"postgresql": "60b39a68a18438603dd5496d", +"motherduck": "60b39a68a18438603dd5496d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "7a007e6ff9e9465c9d7fadea", +"postgresql": "7a007e6ff9e9465c9d7fadea", "snowflake": "60b39a68a18438603dd5496d", "tsql": "60b39a68a18438603dd5496d" } @@ -22091,8 +22091,8 @@ "databricks": "126765943545b2608ebe7128", "duckdb": "126765943545b2608ebe7128", "generic": "126765943545b2608ebe7128", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "126765943545b2608ebe7128", +"motherduck": "126765943545b2608ebe7128", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "126765943545b2608ebe7128", "postgresql": "126765943545b2608ebe7128", "snowflake": "126765943545b2608ebe7128", @@ -22108,8 +22108,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -22122,11 +22122,11 @@ "DuckDB": "9c2cff8be7225cf79883ca65", "None": "9c2cff8be7225cf79883ca65", "bigquery": "9c2cff8be7225cf79883ca65", -"databricks": "901ac89f8d9ec18bd3a61e4b", +"databricks": "9c2cff8be7225cf79883ca65", "duckdb": "9c2cff8be7225cf79883ca65", "generic": "9c2cff8be7225cf79883ca65", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "901ac89f8d9ec18bd3a61e4b", +"motherduck": "9c2cff8be7225cf79883ca65", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9c2cff8be7225cf79883ca65", "postgresql": "9c2cff8be7225cf79883ca65", "snowflake": "9c2cff8be7225cf79883ca65", @@ -22142,8 +22142,8 @@ "databricks": "cf05d378f282d0abe941a82f", "duckdb": "cf05d378f282d0abe941a82f", "generic": "cf05d378f282d0abe941a82f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cf05d378f282d0abe941a82f", +"motherduck": "cf05d378f282d0abe941a82f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cf05d378f282d0abe941a82f", "postgresql": "cf05d378f282d0abe941a82f", "snowflake": "cf05d378f282d0abe941a82f", @@ -22159,10 +22159,10 @@ "databricks": "4b356542044f1267fcfc487a", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -22176,8 +22176,8 @@ "databricks": "d7b74701deb8b87c32deb7a6", "duckdb": "d7b74701deb8b87c32deb7a6", "generic": "d7b74701deb8b87c32deb7a6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d7b74701deb8b87c32deb7a6", +"motherduck": "d7b74701deb8b87c32deb7a6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d7b74701deb8b87c32deb7a6", "postgresql": "d7b74701deb8b87c32deb7a6", "snowflake": "d7b74701deb8b87c32deb7a6", @@ -22193,8 +22193,8 @@ "databricks": "078c0e4b25cdb89eb4ebd88f", "duckdb": "078c0e4b25cdb89eb4ebd88f", "generic": "078c0e4b25cdb89eb4ebd88f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "078c0e4b25cdb89eb4ebd88f", +"motherduck": "078c0e4b25cdb89eb4ebd88f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "078c0e4b25cdb89eb4ebd88f", "postgresql": "078c0e4b25cdb89eb4ebd88f", "snowflake": "6306b9e88f7b13dbb6a32c00", @@ -22206,14 +22206,14 @@ { "DuckDB": "e46d4be44ab9e500e9190d26", "None": "e46d4be44ab9e500e9190d26", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "e46d4be44ab9e500e9190d26", "duckdb": "e46d4be44ab9e500e9190d26", "generic": "e46d4be44ab9e500e9190d26", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e46d4be44ab9e500e9190d26", -"postgres": "e46d4be44ab9e500e9190d26", -"postgresql": "e46d4be44ab9e500e9190d26", +"motherduck": "e46d4be44ab9e500e9190d26", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "251aba3fef503ce64556c701", +"postgresql": "251aba3fef503ce64556c701", "snowflake": "e46d4be44ab9e500e9190d26", "tsql": "e46d4be44ab9e500e9190d26" } @@ -22222,17 +22222,17 @@ "type", { "DuckDB": "dcd0570bcd1ca6e7cbe9b206", -"None": "dcd0570bcd1ca6e7cbe9b206", -"bigquery": "3b4c01f94cca3bf195a3fd00", -"databricks": "526d6b30a5f27d05c0a26aa0", +"None": "85a8004226c2b77c6b697bab", +"bigquery": "85a8004226c2b77c6b697bab", +"databricks": "85a8004226c2b77c6b697bab", "duckdb": "dcd0570bcd1ca6e7cbe9b206", -"generic": "dcd0570bcd1ca6e7cbe9b206", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a5a929e25b7d0dae21e9bb65", -"postgres": "dcd0570bcd1ca6e7cbe9b206", -"postgresql": "dcd0570bcd1ca6e7cbe9b206", +"generic": "85a8004226c2b77c6b697bab", +"motherduck": "dcd0570bcd1ca6e7cbe9b206", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "85a8004226c2b77c6b697bab", +"postgresql": "85a8004226c2b77c6b697bab", "snowflake": "339dae99bd73c126e700c594", -"tsql": "dcd0570bcd1ca6e7cbe9b206" +"tsql": "85a8004226c2b77c6b697bab" } ], [ @@ -22244,8 +22244,8 @@ "databricks": "00402a032fb29ce84ecc4615", "duckdb": "00402a032fb29ce84ecc4615", "generic": "00402a032fb29ce84ecc4615", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "00402a032fb29ce84ecc4615", +"motherduck": "00402a032fb29ce84ecc4615", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "00402a032fb29ce84ecc4615", "postgresql": "00402a032fb29ce84ecc4615", "snowflake": "00402a032fb29ce84ecc4615", @@ -22261,8 +22261,8 @@ "databricks": "52921fa22fa8a5b698ef368f", "duckdb": "52921fa22fa8a5b698ef368f", "generic": "52921fa22fa8a5b698ef368f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "52921fa22fa8a5b698ef368f", +"motherduck": "52921fa22fa8a5b698ef368f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "52921fa22fa8a5b698ef368f", "postgresql": "52921fa22fa8a5b698ef368f", "snowflake": "52921fa22fa8a5b698ef368f", @@ -22278,8 +22278,8 @@ "databricks": "4fb572024031cb7631777be1", "duckdb": "4114b18e7e26d16613331fa7", "generic": "4114b18e7e26d16613331fa7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4114b18e7e26d16613331fa7", +"motherduck": "4114b18e7e26d16613331fa7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4114b18e7e26d16613331fa7", "postgresql": "4114b18e7e26d16613331fa7", "snowflake": "4114b18e7e26d16613331fa7", @@ -22295,8 +22295,8 @@ "databricks": "5aa51d84ac5e6c7a9306f348", "duckdb": "5aa51d84ac5e6c7a9306f348", "generic": "5aa51d84ac5e6c7a9306f348", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5aa51d84ac5e6c7a9306f348", +"motherduck": "5aa51d84ac5e6c7a9306f348", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5aa51d84ac5e6c7a9306f348", "postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "5aa51d84ac5e6c7a9306f348", @@ -22312,8 +22312,8 @@ "databricks": "58f6dfd7cabdc84b33dc2a96", "duckdb": "630c8b82ec44c7208a6df405", "generic": "fa5c71c0d2354f76510bf3e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "58f6dfd7cabdc84b33dc2a96", +"motherduck": "630c8b82ec44c7208a6df405", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "630c8b82ec44c7208a6df405", "postgresql": "630c8b82ec44c7208a6df405", "snowflake": "8efe0c6c8c4444b8fe68129b", @@ -22325,14 +22325,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -22346,8 +22346,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -22359,14 +22359,14 @@ { "DuckDB": "dbd808c57538b7d212ed9a7c", "None": "dbd808c57538b7d212ed9a7c", -"bigquery": "dbd808c57538b7d212ed9a7c", +"bigquery": "c74425c5ce650ac69c83ca5b", "databricks": "dbd808c57538b7d212ed9a7c", "duckdb": "dbd808c57538b7d212ed9a7c", "generic": "dbd808c57538b7d212ed9a7c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dbd808c57538b7d212ed9a7c", -"postgres": "dbd808c57538b7d212ed9a7c", -"postgresql": "dbd808c57538b7d212ed9a7c", +"motherduck": "dbd808c57538b7d212ed9a7c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "932963c69958d2a639768fd3", +"postgresql": "932963c69958d2a639768fd3", "snowflake": "dbd808c57538b7d212ed9a7c", "tsql": "dbd808c57538b7d212ed9a7c" } @@ -22380,8 +22380,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -22397,8 +22397,8 @@ "databricks": "79278352c17c9de0f5a28af5", "duckdb": "79278352c17c9de0f5a28af5", "generic": "79278352c17c9de0f5a28af5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "79278352c17c9de0f5a28af5", +"motherduck": "79278352c17c9de0f5a28af5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "79278352c17c9de0f5a28af5", "postgresql": "79278352c17c9de0f5a28af5", "snowflake": "97a20344d811fb77c7690863", @@ -22414,8 +22414,8 @@ "databricks": "bbc7d9037d6f0383eb14d7eb", "duckdb": "bbc7d9037d6f0383eb14d7eb", "generic": "bbc7d9037d6f0383eb14d7eb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bbc7d9037d6f0383eb14d7eb", +"motherduck": "bbc7d9037d6f0383eb14d7eb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "bbc7d9037d6f0383eb14d7eb", "postgresql": "bbc7d9037d6f0383eb14d7eb", "snowflake": "bbc7d9037d6f0383eb14d7eb", @@ -22427,14 +22427,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -22448,8 +22448,8 @@ "databricks": "cb06a5553de70a4924ed6600", "duckdb": "cb06a5553de70a4924ed6600", "generic": "cb06a5553de70a4924ed6600", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cb06a5553de70a4924ed6600", +"motherduck": "cb06a5553de70a4924ed6600", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cb06a5553de70a4924ed6600", "postgresql": "cb06a5553de70a4924ed6600", "snowflake": "cb06a5553de70a4924ed6600", @@ -22461,14 +22461,14 @@ { "DuckDB": "38b2ec94bf02be40007ce525", "None": "38b2ec94bf02be40007ce525", -"bigquery": "38b2ec94bf02be40007ce525", +"bigquery": "850deea990b52b82b36a9848", "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", -"postgres": "38b2ec94bf02be40007ce525", -"postgresql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "850deea990b52b82b36a9848", +"postgresql": "850deea990b52b82b36a9848", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "38b2ec94bf02be40007ce525" } @@ -22478,14 +22478,14 @@ { "DuckDB": "7cd31289a5b99b5a5e24492d", "None": "7cd31289a5b99b5a5e24492d", -"bigquery": "7cd31289a5b99b5a5e24492d", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "7cd31289a5b99b5a5e24492d", "duckdb": "7cd31289a5b99b5a5e24492d", "generic": "7cd31289a5b99b5a5e24492d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7cd31289a5b99b5a5e24492d", -"postgres": "7cd31289a5b99b5a5e24492d", -"postgresql": "7cd31289a5b99b5a5e24492d", +"motherduck": "7cd31289a5b99b5a5e24492d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "c5bdaf88df7ef3d9ae693106", +"postgresql": "c5bdaf88df7ef3d9ae693106", "snowflake": "7cd31289a5b99b5a5e24492d", "tsql": "7cd31289a5b99b5a5e24492d" } @@ -22499,8 +22499,8 @@ "databricks": "4e7908098c87141abb62306c", "duckdb": "1eb7826ddab37fc01598a8e9", "generic": "4e7908098c87141abb62306c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "1eb7826ddab37fc01598a8e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8ca050e9a43027521099ab95", "postgresql": "8ca050e9a43027521099ab95", "snowflake": "92f647d5460e4e2e7e25fb6d", @@ -22516,8 +22516,8 @@ "databricks": "a0131c4099eb4915760bab4a", "duckdb": "a0131c4099eb4915760bab4a", "generic": "a0131c4099eb4915760bab4a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a0131c4099eb4915760bab4a", +"motherduck": "a0131c4099eb4915760bab4a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a0131c4099eb4915760bab4a", "postgresql": "a0131c4099eb4915760bab4a", "snowflake": "a0131c4099eb4915760bab4a", @@ -22529,14 +22529,14 @@ { "DuckDB": "b561884132dbe7e42b719110", "None": "b561884132dbe7e42b719110", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -22550,10 +22550,10 @@ "databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -22567,8 +22567,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -22584,8 +22584,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -22601,8 +22601,8 @@ "databricks": "86b7927f577e56d61eb9f879", "duckdb": "86b7927f577e56d61eb9f879", "generic": "86b7927f577e56d61eb9f879", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "86b7927f577e56d61eb9f879", +"motherduck": "86b7927f577e56d61eb9f879", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "86b7927f577e56d61eb9f879", "postgresql": "86b7927f577e56d61eb9f879", "snowflake": "86b7927f577e56d61eb9f879", @@ -22618,8 +22618,8 @@ "databricks": "c82815abe30b3d099903f838", "duckdb": "c82815abe30b3d099903f838", "generic": "c82815abe30b3d099903f838", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c82815abe30b3d099903f838", +"motherduck": "c82815abe30b3d099903f838", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c82815abe30b3d099903f838", "postgresql": "c82815abe30b3d099903f838", "snowflake": "c82815abe30b3d099903f838", @@ -22635,8 +22635,8 @@ "databricks": "7d23077fb72f7bac9a49a632", "duckdb": "7d23077fb72f7bac9a49a632", "generic": "7d23077fb72f7bac9a49a632", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7d23077fb72f7bac9a49a632", +"motherduck": "7d23077fb72f7bac9a49a632", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7d23077fb72f7bac9a49a632", "postgresql": "7d23077fb72f7bac9a49a632", "snowflake": "7d23077fb72f7bac9a49a632", @@ -22652,10 +22652,10 @@ "databricks": "177c462b3a0270e894c8111c", "duckdb": "177c462b3a0270e894c8111c", "generic": "177c462b3a0270e894c8111c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "177c462b3a0270e894c8111c", -"postgres": "177c462b3a0270e894c8111c", -"postgresql": "177c462b3a0270e894c8111c", +"motherduck": "177c462b3a0270e894c8111c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "2625125fd986796ed33a61b7", +"postgresql": "2625125fd986796ed33a61b7", "snowflake": "177c462b3a0270e894c8111c", "tsql": "177c462b3a0270e894c8111c" } @@ -22669,8 +22669,8 @@ "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fe23594c781e79cbb0c8927a", "postgresql": "fe23594c781e79cbb0c8927a", "snowflake": "38b2ec94bf02be40007ce525", @@ -22682,14 +22682,14 @@ { "DuckDB": "08736485547ba339930697e9", "None": "08736485547ba339930697e9", -"bigquery": "08736485547ba339930697e9", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "08736485547ba339930697e9", "duckdb": "08736485547ba339930697e9", "generic": "08736485547ba339930697e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "08736485547ba339930697e9", -"postgres": "08736485547ba339930697e9", -"postgresql": "08736485547ba339930697e9", +"motherduck": "08736485547ba339930697e9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eecd452b4f5f3f4133d0bd0a", +"postgresql": "eecd452b4f5f3f4133d0bd0a", "snowflake": "08736485547ba339930697e9", "tsql": "08736485547ba339930697e9" } @@ -22703,8 +22703,8 @@ "databricks": "8d81e8dacd544a5e07c3b1ea", "duckdb": "8d81e8dacd544a5e07c3b1ea", "generic": "8d81e8dacd544a5e07c3b1ea", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8d81e8dacd544a5e07c3b1ea", +"motherduck": "8d81e8dacd544a5e07c3b1ea", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8d81e8dacd544a5e07c3b1ea", "postgresql": "8d81e8dacd544a5e07c3b1ea", "snowflake": "8d81e8dacd544a5e07c3b1ea", @@ -22720,10 +22720,10 @@ "databricks": "3e611643f0beab4953c4e09b", "duckdb": "3e611643f0beab4953c4e09b", "generic": "3e611643f0beab4953c4e09b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3e611643f0beab4953c4e09b", -"postgres": "3e611643f0beab4953c4e09b", -"postgresql": "3e611643f0beab4953c4e09b", +"motherduck": "3e611643f0beab4953c4e09b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cae477ce59c72869f37a9a40", +"postgresql": "cae477ce59c72869f37a9a40", "snowflake": "3e611643f0beab4953c4e09b", "tsql": "3e611643f0beab4953c4e09b" } @@ -22737,8 +22737,8 @@ "databricks": "e2eee356d05f550b28bf85ad", "duckdb": "e2eee356d05f550b28bf85ad", "generic": "e2eee356d05f550b28bf85ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e2eee356d05f550b28bf85ad", +"motherduck": "e2eee356d05f550b28bf85ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e2eee356d05f550b28bf85ad", "postgresql": "e2eee356d05f550b28bf85ad", "snowflake": "e2eee356d05f550b28bf85ad", @@ -22754,10 +22754,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -22771,8 +22771,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -22788,8 +22788,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -22805,8 +22805,8 @@ "databricks": "6ebf45a787a223241c8d9a47", "duckdb": "6ebf45a787a223241c8d9a47", "generic": "6ebf45a787a223241c8d9a47", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6ebf45a787a223241c8d9a47", +"motherduck": "6ebf45a787a223241c8d9a47", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6ebf45a787a223241c8d9a47", "postgresql": "6ebf45a787a223241c8d9a47", "snowflake": "6ebf45a787a223241c8d9a47", @@ -22822,8 +22822,8 @@ "databricks": "cd9c38fc2cf84c8551e29278", "duckdb": "cd9c38fc2cf84c8551e29278", "generic": "cd9c38fc2cf84c8551e29278", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cd9c38fc2cf84c8551e29278", +"motherduck": "cd9c38fc2cf84c8551e29278", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cd9c38fc2cf84c8551e29278", "postgresql": "cd9c38fc2cf84c8551e29278", "snowflake": "cd9c38fc2cf84c8551e29278", @@ -22839,8 +22839,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -22850,18 +22850,18 @@ [ "type", { -"DuckDB": "944971a819b2498d3ecdc71d", -"None": "90882506184f4f707c46fdea", +"DuckDB": "225fc4f7b28377ba911e4a47", +"None": "a05c10798b5cb323b7dc3ed4", "bigquery": "f1cb74b07e7c8c7243c9034b", -"databricks": "37decbb811ce5abeffb05b4e", -"duckdb": "944971a819b2498d3ecdc71d", -"generic": "90882506184f4f707c46fdea", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "90882506184f4f707c46fdea", -"postgres": "61f2d73857de8729b66f99d2", -"postgresql": "61f2d73857de8729b66f99d2", +"databricks": "a05c10798b5cb323b7dc3ed4", +"duckdb": "225fc4f7b28377ba911e4a47", +"generic": "a05c10798b5cb323b7dc3ed4", +"motherduck": "225fc4f7b28377ba911e4a47", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "4e87e4788e5ac719db00d4e4", +"postgresql": "4e87e4788e5ac719db00d4e4", "snowflake": "225fc4f7b28377ba911e4a47", -"tsql": "5fb4ec49639b38cdf3ccfc38" +"tsql": "1a635a1b4dbecf67ace34158" } ], [ @@ -22869,14 +22869,14 @@ { "DuckDB": "dbf9ebbc7016f5ac5df2817e", "None": "dbf9ebbc7016f5ac5df2817e", -"bigquery": "dbf9ebbc7016f5ac5df2817e", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "dbf9ebbc7016f5ac5df2817e", "duckdb": "dbf9ebbc7016f5ac5df2817e", "generic": "dbf9ebbc7016f5ac5df2817e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "dbf9ebbc7016f5ac5df2817e", -"postgres": "dbf9ebbc7016f5ac5df2817e", -"postgresql": "dbf9ebbc7016f5ac5df2817e", +"motherduck": "dbf9ebbc7016f5ac5df2817e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "4cccf86e62d627a7a8b57726", +"postgresql": "4cccf86e62d627a7a8b57726", "snowflake": "dbf9ebbc7016f5ac5df2817e", "tsql": "dbf9ebbc7016f5ac5df2817e" } @@ -22890,8 +22890,8 @@ "databricks": "5debd5336e9bc9ec70e731b7", "duckdb": "5debd5336e9bc9ec70e731b7", "generic": "5debd5336e9bc9ec70e731b7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5debd5336e9bc9ec70e731b7", +"motherduck": "5debd5336e9bc9ec70e731b7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5debd5336e9bc9ec70e731b7", "postgresql": "5debd5336e9bc9ec70e731b7", "snowflake": "5debd5336e9bc9ec70e731b7", @@ -22903,14 +22903,14 @@ { "DuckDB": "d2c97d221599d038d9875873", "None": "d2c97d221599d038d9875873", -"bigquery": "d2c97d221599d038d9875873", +"bigquery": "55155766823b6a3a5e882fc2", "databricks": "d2c97d221599d038d9875873", "duckdb": "d2c97d221599d038d9875873", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "d2c97d221599d038d9875873", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -22924,8 +22924,8 @@ "databricks": "1fdc0b9f28b115c175e88d46", "duckdb": "1fdc0b9f28b115c175e88d46", "generic": "1fdc0b9f28b115c175e88d46", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1fdc0b9f28b115c175e88d46", +"motherduck": "1fdc0b9f28b115c175e88d46", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1fdc0b9f28b115c175e88d46", "postgresql": "1fdc0b9f28b115c175e88d46", "snowflake": "1fdc0b9f28b115c175e88d46", @@ -22941,8 +22941,8 @@ "databricks": "f38ef32a483f1c4f89ae309e", "duckdb": "f38ef32a483f1c4f89ae309e", "generic": "f38ef32a483f1c4f89ae309e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f38ef32a483f1c4f89ae309e", +"motherduck": "f38ef32a483f1c4f89ae309e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f38ef32a483f1c4f89ae309e", "postgresql": "f38ef32a483f1c4f89ae309e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -22958,8 +22958,8 @@ "databricks": "4281b54de06f589b3507035a", "duckdb": "5620e893a908c6743b863bf6", "generic": "5620e893a908c6743b863bf6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4281b54de06f589b3507035a", +"motherduck": "5620e893a908c6743b863bf6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5620e893a908c6743b863bf6", "postgresql": "5620e893a908c6743b863bf6", "snowflake": "5620e893a908c6743b863bf6", @@ -22975,8 +22975,8 @@ "databricks": "c0d59e65280108f563dbb04b", "duckdb": "c0d59e65280108f563dbb04b", "generic": "c0d59e65280108f563dbb04b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c0d59e65280108f563dbb04b", +"motherduck": "c0d59e65280108f563dbb04b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c0d59e65280108f563dbb04b", "postgresql": "c0d59e65280108f563dbb04b", "snowflake": "c0d59e65280108f563dbb04b", @@ -22992,10 +22992,10 @@ "databricks": "d16d75bc8832411932952499", "duckdb": "d16d75bc8832411932952499", "generic": "d16d75bc8832411932952499", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d16d75bc8832411932952499", -"postgres": "d16d75bc8832411932952499", -"postgresql": "d16d75bc8832411932952499", +"motherduck": "d16d75bc8832411932952499", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9372facf3130ca098b0fd00a", +"postgresql": "9372facf3130ca098b0fd00a", "snowflake": "d16d75bc8832411932952499", "tsql": "d16d75bc8832411932952499" } @@ -23004,17 +23004,17 @@ "type", { "DuckDB": "5abf4ec799a2ec32fb10da7e", -"None": "5abf4ec799a2ec32fb10da7e", +"None": "de89632e277dbc8c97115419", "bigquery": "5abf4ec799a2ec32fb10da7e", -"databricks": "5abf4ec799a2ec32fb10da7e", +"databricks": "de89632e277dbc8c97115419", "duckdb": "5abf4ec799a2ec32fb10da7e", -"generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"generic": "de89632e277dbc8c97115419", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", -"tsql": "5abf4ec799a2ec32fb10da7e" +"tsql": "de89632e277dbc8c97115419" } ], [ @@ -23026,8 +23026,8 @@ "databricks": "6cffd1617edd6454241ee732", "duckdb": "6cffd1617edd6454241ee732", "generic": "6cffd1617edd6454241ee732", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6cffd1617edd6454241ee732", +"motherduck": "6cffd1617edd6454241ee732", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6cffd1617edd6454241ee732", "postgresql": "6cffd1617edd6454241ee732", "snowflake": "6cffd1617edd6454241ee732", @@ -23043,10 +23043,10 @@ "databricks": "a686bb6db17ab079cfc79224", "duckdb": "a686bb6db17ab079cfc79224", "generic": "a686bb6db17ab079cfc79224", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a686bb6db17ab079cfc79224", -"postgres": "a686bb6db17ab079cfc79224", -"postgresql": "a686bb6db17ab079cfc79224", +"motherduck": "a686bb6db17ab079cfc79224", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "74a30f6aa9def0b58afc616c", +"postgresql": "74a30f6aa9def0b58afc616c", "snowflake": "1096c3c8687c8e8eb538ff74", "tsql": "a686bb6db17ab079cfc79224" } @@ -23060,8 +23060,8 @@ "databricks": "c752fd9fe2ad3b86b8b899da", "duckdb": "c752fd9fe2ad3b86b8b899da", "generic": "c752fd9fe2ad3b86b8b899da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c752fd9fe2ad3b86b8b899da", +"motherduck": "c752fd9fe2ad3b86b8b899da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -23077,8 +23077,8 @@ "databricks": "b52d5a83269c4ad64bd3416e", "duckdb": "b52d5a83269c4ad64bd3416e", "generic": "b52d5a83269c4ad64bd3416e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b52d5a83269c4ad64bd3416e", +"motherduck": "b52d5a83269c4ad64bd3416e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b52d5a83269c4ad64bd3416e", "postgresql": "b52d5a83269c4ad64bd3416e", "snowflake": "b52d5a83269c4ad64bd3416e", @@ -23088,16 +23088,16 @@ [ "type", { -"DuckDB": "aff2599f42411f45ff949ed9", -"None": "be47a14e7523acb3fcbd4b5b", +"DuckDB": "e3729d015bce3152bae7cd02", +"None": "f98c7776673323299fbfe6b7", "bigquery": "f98c7776673323299fbfe6b7", -"databricks": "2ed848a19b2b41fdf76d9a87", -"duckdb": "aff2599f42411f45ff949ed9", -"generic": "be47a14e7523acb3fcbd4b5b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "aff2599f42411f45ff949ed9", -"postgresql": "aff2599f42411f45ff949ed9", +"databricks": "f98c7776673323299fbfe6b7", +"duckdb": "e3729d015bce3152bae7cd02", +"generic": "f98c7776673323299fbfe6b7", +"motherduck": "e3729d015bce3152bae7cd02", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e3729d015bce3152bae7cd02", +"postgresql": "e3729d015bce3152bae7cd02", "snowflake": "a2a30dfc031641a60ba584fa", "tsql": "d1dc9a140dbe2eb1874c6ad7" } @@ -23106,17 +23106,17 @@ "type", { "DuckDB": "f2ed52f6bf46b4db9343551c", -"None": "f2ed52f6bf46b4db9343551c", -"bigquery": "f10bdd4d40eb495cbcecd31d", -"databricks": "f2ed52f6bf46b4db9343551c", +"None": "eeb8bd6105754e78339cd228", +"bigquery": "eeb8bd6105754e78339cd228", +"databricks": "eeb8bd6105754e78339cd228", "duckdb": "f2ed52f6bf46b4db9343551c", -"generic": "f2ed52f6bf46b4db9343551c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f2ed52f6bf46b4db9343551c", -"postgres": "f2ed52f6bf46b4db9343551c", -"postgresql": "f2ed52f6bf46b4db9343551c", +"generic": "eeb8bd6105754e78339cd228", +"motherduck": "f2ed52f6bf46b4db9343551c", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eeb8bd6105754e78339cd228", +"postgresql": "eeb8bd6105754e78339cd228", "snowflake": "f2ed52f6bf46b4db9343551c", -"tsql": "f2ed52f6bf46b4db9343551c" +"tsql": "eeb8bd6105754e78339cd228" } ], [ @@ -23124,14 +23124,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -23145,8 +23145,8 @@ "databricks": "88ee4a69672b4b9b3aa21e3d", "duckdb": "88ee4a69672b4b9b3aa21e3d", "generic": "88ee4a69672b4b9b3aa21e3d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "88ee4a69672b4b9b3aa21e3d", +"motherduck": "88ee4a69672b4b9b3aa21e3d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "88ee4a69672b4b9b3aa21e3d", "postgresql": "88ee4a69672b4b9b3aa21e3d", "snowflake": "88ee4a69672b4b9b3aa21e3d", @@ -23162,8 +23162,8 @@ "databricks": "ab5ad6180652b3b00d1f5b01", "duckdb": "ab5ad6180652b3b00d1f5b01", "generic": "ab5ad6180652b3b00d1f5b01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ab5ad6180652b3b00d1f5b01", +"motherduck": "ab5ad6180652b3b00d1f5b01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ab5ad6180652b3b00d1f5b01", "postgresql": "ab5ad6180652b3b00d1f5b01", "snowflake": "226beccc1d430773f4e67b70", @@ -23179,8 +23179,8 @@ "databricks": "555111267818cbd3d0b07105", "duckdb": "555111267818cbd3d0b07105", "generic": "555111267818cbd3d0b07105", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "555111267818cbd3d0b07105", +"motherduck": "555111267818cbd3d0b07105", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "555111267818cbd3d0b07105", "postgresql": "555111267818cbd3d0b07105", "snowflake": "555111267818cbd3d0b07105", @@ -23192,14 +23192,14 @@ { "DuckDB": "18db5afe2c1e80d1ee652b54", "None": "18db5afe2c1e80d1ee652b54", -"bigquery": "e1c36a5cdd2427ff2b089ac9", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "18db5afe2c1e80d1ee652b54", "duckdb": "18db5afe2c1e80d1ee652b54", "generic": "18db5afe2c1e80d1ee652b54", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "18db5afe2c1e80d1ee652b54", -"postgres": "18db5afe2c1e80d1ee652b54", -"postgresql": "18db5afe2c1e80d1ee652b54", +"motherduck": "18db5afe2c1e80d1ee652b54", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "47d64a5994d42a2b577166ea", +"postgresql": "47d64a5994d42a2b577166ea", "snowflake": "18db5afe2c1e80d1ee652b54", "tsql": "18db5afe2c1e80d1ee652b54" } @@ -23213,8 +23213,8 @@ "databricks": "9aff7fcc7d550e1a8d105471", "duckdb": "9aff7fcc7d550e1a8d105471", "generic": "9aff7fcc7d550e1a8d105471", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9aff7fcc7d550e1a8d105471", +"motherduck": "9aff7fcc7d550e1a8d105471", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9aff7fcc7d550e1a8d105471", "postgresql": "9aff7fcc7d550e1a8d105471", "snowflake": "9aff7fcc7d550e1a8d105471", @@ -23230,8 +23230,8 @@ "databricks": "e8b6e0bd2ded7b9eec6209e4", "duckdb": "e8b6e0bd2ded7b9eec6209e4", "generic": "e8b6e0bd2ded7b9eec6209e4", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e8b6e0bd2ded7b9eec6209e4", +"motherduck": "e8b6e0bd2ded7b9eec6209e4", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e8b6e0bd2ded7b9eec6209e4", "postgresql": "e8b6e0bd2ded7b9eec6209e4", "snowflake": "e8b6e0bd2ded7b9eec6209e4", @@ -23243,12 +23243,12 @@ { "DuckDB": "e08a957777bd278f921ca0ee", "None": "e08a957777bd278f921ca0ee", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "e08a957777bd278f921ca0ee", "duckdb": "e08a957777bd278f921ca0ee", "generic": "e08a957777bd278f921ca0ee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e08a957777bd278f921ca0ee", +"motherduck": "e08a957777bd278f921ca0ee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e08a957777bd278f921ca0ee", "postgresql": "e08a957777bd278f921ca0ee", "snowflake": "e08a957777bd278f921ca0ee", @@ -23264,8 +23264,8 @@ "databricks": "da72f481881cd577b304e9e7", "duckdb": "da72f481881cd577b304e9e7", "generic": "da72f481881cd577b304e9e7", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "da72f481881cd577b304e9e7", +"motherduck": "da72f481881cd577b304e9e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "da72f481881cd577b304e9e7", "postgresql": "da72f481881cd577b304e9e7", "snowflake": "da72f481881cd577b304e9e7", @@ -23281,8 +23281,8 @@ "databricks": "b0791eeb8731828818f87f78", "duckdb": "b0791eeb8731828818f87f78", "generic": "b0791eeb8731828818f87f78", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b0791eeb8731828818f87f78", +"motherduck": "b0791eeb8731828818f87f78", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b0791eeb8731828818f87f78", "postgresql": "b0791eeb8731828818f87f78", "snowflake": "b0791eeb8731828818f87f78", @@ -23298,8 +23298,8 @@ "databricks": "d4b3585f1e47f5f60a2c28ee", "duckdb": "d4b3585f1e47f5f60a2c28ee", "generic": "d4b3585f1e47f5f60a2c28ee", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d4b3585f1e47f5f60a2c28ee", +"motherduck": "d4b3585f1e47f5f60a2c28ee", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d4b3585f1e47f5f60a2c28ee", "postgresql": "d4b3585f1e47f5f60a2c28ee", "snowflake": "d4b3585f1e47f5f60a2c28ee", @@ -23311,12 +23311,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -23328,14 +23328,14 @@ { "DuckDB": "4b356542044f1267fcfc487a", "None": "4b356542044f1267fcfc487a", -"bigquery": "4b356542044f1267fcfc487a", +"bigquery": "a5656d70e208254d1d3e9b4f", "databricks": "4b356542044f1267fcfc487a", "duckdb": "4b356542044f1267fcfc487a", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "4b356542044f1267fcfc487a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -23349,8 +23349,8 @@ "databricks": "5abf4ec799a2ec32fb10da7e", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "5abf4ec799a2ec32fb10da7e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5abf4ec799a2ec32fb10da7e", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5abf4ec799a2ec32fb10da7e", "postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -23366,8 +23366,8 @@ "databricks": "7ca149227eff68da5bfa0404", "duckdb": "7ca149227eff68da5bfa0404", "generic": "7ca149227eff68da5bfa0404", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7ca149227eff68da5bfa0404", +"motherduck": "7ca149227eff68da5bfa0404", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7ca149227eff68da5bfa0404", "postgresql": "7ca149227eff68da5bfa0404", "snowflake": "7ca149227eff68da5bfa0404", @@ -23383,8 +23383,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -23400,8 +23400,8 @@ "databricks": "19cc45192479751e55bffbfe", "duckdb": "19cc45192479751e55bffbfe", "generic": "19cc45192479751e55bffbfe", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19cc45192479751e55bffbfe", +"motherduck": "19cc45192479751e55bffbfe", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19cc45192479751e55bffbfe", "postgresql": "19cc45192479751e55bffbfe", "snowflake": "19cc45192479751e55bffbfe", @@ -23417,8 +23417,8 @@ "databricks": "300e2b67af892b463d85f9cd", "duckdb": "300e2b67af892b463d85f9cd", "generic": "300e2b67af892b463d85f9cd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "300e2b67af892b463d85f9cd", +"motherduck": "300e2b67af892b463d85f9cd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "300e2b67af892b463d85f9cd", "postgresql": "300e2b67af892b463d85f9cd", "snowflake": "300e2b67af892b463d85f9cd", @@ -23434,8 +23434,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "1e75e5227bea0ae294ac7af1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19bdd92ea0cc5fb028657510", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "19bdd92ea0cc5fb028657510", "postgresql": "19bdd92ea0cc5fb028657510", "snowflake": "8ff614b7306c0edbe36ce00b", @@ -23451,8 +23451,8 @@ "databricks": "94f6df33eccb97170eccb01b", "duckdb": "94f6df33eccb97170eccb01b", "generic": "94f6df33eccb97170eccb01b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "94f6df33eccb97170eccb01b", +"motherduck": "94f6df33eccb97170eccb01b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "94f6df33eccb97170eccb01b", "postgresql": "94f6df33eccb97170eccb01b", "snowflake": "94f6df33eccb97170eccb01b", @@ -23468,10 +23468,10 @@ "databricks": "19a1a80ffc06169f32694de8", "duckdb": "19a1a80ffc06169f32694de8", "generic": "19a1a80ffc06169f32694de8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "19a1a80ffc06169f32694de8", -"postgres": "19a1a80ffc06169f32694de8", -"postgresql": "19a1a80ffc06169f32694de8", +"motherduck": "19a1a80ffc06169f32694de8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "ab1f5f46e69284778bd66d58", +"postgresql": "ab1f5f46e69284778bd66d58", "snowflake": "19a1a80ffc06169f32694de8", "tsql": "19a1a80ffc06169f32694de8" } @@ -23485,8 +23485,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -23498,14 +23498,14 @@ { "DuckDB": "de89632e277dbc8c97115419", "None": "de89632e277dbc8c97115419", -"bigquery": "de89632e277dbc8c97115419", +"bigquery": "5abf4ec799a2ec32fb10da7e", "databricks": "de89632e277dbc8c97115419", "duckdb": "de89632e277dbc8c97115419", "generic": "de89632e277dbc8c97115419", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "de89632e277dbc8c97115419", -"postgres": "de89632e277dbc8c97115419", -"postgresql": "de89632e277dbc8c97115419", +"motherduck": "de89632e277dbc8c97115419", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5abf4ec799a2ec32fb10da7e", +"postgresql": "5abf4ec799a2ec32fb10da7e", "snowflake": "4b356542044f1267fcfc487a", "tsql": "de89632e277dbc8c97115419" } @@ -23519,8 +23519,8 @@ "databricks": "2444c03418f9f1b2f23f2ec8", "duckdb": "2444c03418f9f1b2f23f2ec8", "generic": "2444c03418f9f1b2f23f2ec8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2444c03418f9f1b2f23f2ec8", +"motherduck": "2444c03418f9f1b2f23f2ec8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2444c03418f9f1b2f23f2ec8", "postgresql": "2444c03418f9f1b2f23f2ec8", "snowflake": "2444c03418f9f1b2f23f2ec8", @@ -23536,10 +23536,10 @@ "databricks": "d2c97d221599d038d9875873", "duckdb": "f7fca17b74cc50ae3de8c826", "generic": "d2c97d221599d038d9875873", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d2c97d221599d038d9875873", -"postgres": "d2c97d221599d038d9875873", -"postgresql": "d2c97d221599d038d9875873", +"motherduck": "f7fca17b74cc50ae3de8c826", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55155766823b6a3a5e882fc2", +"postgresql": "55155766823b6a3a5e882fc2", "snowflake": "d2c97d221599d038d9875873", "tsql": "d2c97d221599d038d9875873" } @@ -23549,14 +23549,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -23566,14 +23566,14 @@ { "DuckDB": "b561884132dbe7e42b719110", "None": "b561884132dbe7e42b719110", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "b561884132dbe7e42b719110", "duckdb": "b561884132dbe7e42b719110", "generic": "b561884132dbe7e42b719110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b561884132dbe7e42b719110", -"postgres": "b561884132dbe7e42b719110", -"postgresql": "b561884132dbe7e42b719110", +"motherduck": "b561884132dbe7e42b719110", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5aa51d84ac5e6c7a9306f348", +"postgresql": "5aa51d84ac5e6c7a9306f348", "snowflake": "b561884132dbe7e42b719110", "tsql": "b561884132dbe7e42b719110" } @@ -23587,10 +23587,10 @@ "databricks": "f8219c960720043f0662a603", "duckdb": "35a9c5c1419bff507cf65ae2", "generic": "f8219c960720043f0662a603", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f8219c960720043f0662a603", -"postgres": "f8219c960720043f0662a603", -"postgresql": "f8219c960720043f0662a603", +"motherduck": "35a9c5c1419bff507cf65ae2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "35a9c5c1419bff507cf65ae2", +"postgresql": "35a9c5c1419bff507cf65ae2", "snowflake": "f8219c960720043f0662a603", "tsql": "f8219c960720043f0662a603" } @@ -23604,10 +23604,10 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "396b2ce32efb5365b393c230", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", -"postgres": "e57ac1541fb51b362cd5b470", -"postgresql": "e57ac1541fb51b362cd5b470", +"motherduck": "396b2ce32efb5365b393c230", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "396b2ce32efb5365b393c230", +"postgresql": "396b2ce32efb5365b393c230", "snowflake": "4b356542044f1267fcfc487a", "tsql": "e57ac1541fb51b362cd5b470" } @@ -23621,8 +23621,8 @@ "databricks": "f16105cbc61d547e6c08efc8", "duckdb": "f16105cbc61d547e6c08efc8", "generic": "f16105cbc61d547e6c08efc8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f16105cbc61d547e6c08efc8", +"motherduck": "f16105cbc61d547e6c08efc8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f16105cbc61d547e6c08efc8", "postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -23638,8 +23638,8 @@ "databricks": "f6140be64a327a15b42b923d", "duckdb": "f6140be64a327a15b42b923d", "generic": "f6140be64a327a15b42b923d", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f6140be64a327a15b42b923d", +"motherduck": "f6140be64a327a15b42b923d", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f6140be64a327a15b42b923d", "postgresql": "f6140be64a327a15b42b923d", "snowflake": "f6140be64a327a15b42b923d", @@ -23655,8 +23655,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -23672,8 +23672,8 @@ "databricks": "25cdfa949946952336077d37", "duckdb": "25cdfa949946952336077d37", "generic": "25cdfa949946952336077d37", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "25cdfa949946952336077d37", +"motherduck": "25cdfa949946952336077d37", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "25cdfa949946952336077d37", "postgresql": "25cdfa949946952336077d37", "snowflake": "25cdfa949946952336077d37", @@ -23689,8 +23689,8 @@ "databricks": "6d7ab232e65fe8d85c7169ae", "duckdb": "6d7ab232e65fe8d85c7169ae", "generic": "6d7ab232e65fe8d85c7169ae", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6d7ab232e65fe8d85c7169ae", +"motherduck": "6d7ab232e65fe8d85c7169ae", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a6659d96bbfa5da75e69e22d", "postgresql": "a6659d96bbfa5da75e69e22d", "snowflake": "6d7ab232e65fe8d85c7169ae", @@ -23706,8 +23706,8 @@ "databricks": "1509a96170cb94e0a99bbf00", "duckdb": "13bc61bc9a039c8b798c5322", "generic": "1509a96170cb94e0a99bbf00", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "13bc61bc9a039c8b798c5322", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "13bc61bc9a039c8b798c5322", "postgresql": "13bc61bc9a039c8b798c5322", "snowflake": "d5cc04b4579263e52cd194bb", @@ -23723,8 +23723,8 @@ "databricks": "d6de1c22b8b95cc863db57ba", "duckdb": "d6de1c22b8b95cc863db57ba", "generic": "d6de1c22b8b95cc863db57ba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d6de1c22b8b95cc863db57ba", +"motherduck": "d6de1c22b8b95cc863db57ba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d6de1c22b8b95cc863db57ba", "postgresql": "d6de1c22b8b95cc863db57ba", "snowflake": "d6de1c22b8b95cc863db57ba", @@ -23740,10 +23740,10 @@ "databricks": "2c6b371b030464f525066893", "duckdb": "2c6b371b030464f525066893", "generic": "2c6b371b030464f525066893", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2c6b371b030464f525066893", -"postgres": "2c6b371b030464f525066893", -"postgresql": "2c6b371b030464f525066893", +"motherduck": "2c6b371b030464f525066893", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3feb479faeec777c2ade8afd", +"postgresql": "3feb479faeec777c2ade8afd", "snowflake": "2c6b371b030464f525066893", "tsql": "2c6b371b030464f525066893" } @@ -23757,8 +23757,8 @@ "databricks": "46ec3e19e05a6cb29854ccac", "duckdb": "35e216b6921bf843994f2eea", "generic": "46ec3e19e05a6cb29854ccac", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "46ec3e19e05a6cb29854ccac", +"motherduck": "35e216b6921bf843994f2eea", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "46ec3e19e05a6cb29854ccac", "postgresql": "46ec3e19e05a6cb29854ccac", "snowflake": "46ec3e19e05a6cb29854ccac", @@ -23770,14 +23770,14 @@ { "DuckDB": "08736485547ba339930697e9", "None": "08736485547ba339930697e9", -"bigquery": "08736485547ba339930697e9", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "08736485547ba339930697e9", "duckdb": "08736485547ba339930697e9", "generic": "08736485547ba339930697e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "08736485547ba339930697e9", -"postgres": "08736485547ba339930697e9", -"postgresql": "08736485547ba339930697e9", +"motherduck": "08736485547ba339930697e9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eecd452b4f5f3f4133d0bd0a", +"postgresql": "eecd452b4f5f3f4133d0bd0a", "snowflake": "08736485547ba339930697e9", "tsql": "08736485547ba339930697e9" } @@ -23787,14 +23787,14 @@ { "DuckDB": "2fcd11f2fdbe6c4498c4833e", "None": "2fcd11f2fdbe6c4498c4833e", -"bigquery": "2fcd11f2fdbe6c4498c4833e", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "2fcd11f2fdbe6c4498c4833e", "duckdb": "2fcd11f2fdbe6c4498c4833e", "generic": "2fcd11f2fdbe6c4498c4833e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2fcd11f2fdbe6c4498c4833e", -"postgres": "2fcd11f2fdbe6c4498c4833e", -"postgresql": "2fcd11f2fdbe6c4498c4833e", +"motherduck": "2fcd11f2fdbe6c4498c4833e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3255b7052c624f34aab6633f", +"postgresql": "3255b7052c624f34aab6633f", "snowflake": "2fcd11f2fdbe6c4498c4833e", "tsql": "2fcd11f2fdbe6c4498c4833e" } @@ -23804,14 +23804,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -23825,8 +23825,8 @@ "databricks": "c844c96e1e3ffc72d5f90df9", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "c844c96e1e3ffc72d5f90df9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28eb0d6de9724959ce5ba43e", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c844c96e1e3ffc72d5f90df9", "postgresql": "c844c96e1e3ffc72d5f90df9", "snowflake": "c844c96e1e3ffc72d5f90df9", @@ -23838,12 +23838,12 @@ { "DuckDB": "5c28019dce48a74708c90a76", "None": "5c28019dce48a74708c90a76", -"bigquery": "f28a96f0eb38ad8874f6ec5d", +"bigquery": "5c28019dce48a74708c90a76", "databricks": "5c28019dce48a74708c90a76", "duckdb": "5c28019dce48a74708c90a76", "generic": "5c28019dce48a74708c90a76", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "5c28019dce48a74708c90a76", +"motherduck": "5c28019dce48a74708c90a76", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "5c28019dce48a74708c90a76", "postgresql": "5c28019dce48a74708c90a76", "snowflake": "a5656d70e208254d1d3e9b4f", @@ -23855,14 +23855,14 @@ { "DuckDB": "2fcd11f2fdbe6c4498c4833e", "None": "2fcd11f2fdbe6c4498c4833e", -"bigquery": "2fcd11f2fdbe6c4498c4833e", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "2fcd11f2fdbe6c4498c4833e", "duckdb": "2fcd11f2fdbe6c4498c4833e", "generic": "2fcd11f2fdbe6c4498c4833e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2fcd11f2fdbe6c4498c4833e", -"postgres": "2fcd11f2fdbe6c4498c4833e", -"postgresql": "2fcd11f2fdbe6c4498c4833e", +"motherduck": "2fcd11f2fdbe6c4498c4833e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3255b7052c624f34aab6633f", +"postgresql": "3255b7052c624f34aab6633f", "snowflake": "2fcd11f2fdbe6c4498c4833e", "tsql": "2fcd11f2fdbe6c4498c4833e" } @@ -23876,8 +23876,8 @@ "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "aab0389e2b5470136660bf8a", "postgresql": "aab0389e2b5470136660bf8a", "snowflake": "97a20344d811fb77c7690863", @@ -23888,17 +23888,17 @@ "type", { "DuckDB": "6f4372a332076048e56d5076", -"None": "6f4372a332076048e56d5076", -"bigquery": "74a30f6aa9def0b58afc616c", -"databricks": "6f4372a332076048e56d5076", +"None": "fa78cfa46a78783d069e2fc5", +"bigquery": "b0477b36767a62145aa9158c", +"databricks": "fa78cfa46a78783d069e2fc5", "duckdb": "6f4372a332076048e56d5076", -"generic": "6f4372a332076048e56d5076", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6f4372a332076048e56d5076", +"generic": "fa78cfa46a78783d069e2fc5", +"motherduck": "6f4372a332076048e56d5076", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "6f4372a332076048e56d5076", "postgresql": "6f4372a332076048e56d5076", "snowflake": "daeeb0f00b3ee4211b8da00f", -"tsql": "6f4372a332076048e56d5076" +"tsql": "fa78cfa46a78783d069e2fc5" } ], [ @@ -23910,8 +23910,8 @@ "databricks": "7a00586f9fdb1d97256bfff8", "duckdb": "7a00586f9fdb1d97256bfff8", "generic": "7a00586f9fdb1d97256bfff8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7a00586f9fdb1d97256bfff8", +"motherduck": "7a00586f9fdb1d97256bfff8", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7a00586f9fdb1d97256bfff8", "postgresql": "7a00586f9fdb1d97256bfff8", "snowflake": "7a00586f9fdb1d97256bfff8", @@ -23927,8 +23927,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -23944,8 +23944,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -23961,8 +23961,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -23974,14 +23974,14 @@ { "DuckDB": "aab0389e2b5470136660bf8a", "None": "aab0389e2b5470136660bf8a", -"bigquery": "c9b810639795a7bfab91b98d", +"bigquery": "a428302e4848ffe877284c84", "databricks": "aab0389e2b5470136660bf8a", "duckdb": "aab0389e2b5470136660bf8a", "generic": "aab0389e2b5470136660bf8a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "aab0389e2b5470136660bf8a", -"postgres": "aab0389e2b5470136660bf8a", -"postgresql": "aab0389e2b5470136660bf8a", +"motherduck": "aab0389e2b5470136660bf8a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f16105cbc61d547e6c08efc8", +"postgresql": "f16105cbc61d547e6c08efc8", "snowflake": "97a20344d811fb77c7690863", "tsql": "aab0389e2b5470136660bf8a" } @@ -23991,12 +23991,12 @@ { "DuckDB": "62368fc15cefd5807807b209", "None": "62368fc15cefd5807807b209", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "62368fc15cefd5807807b209", "duckdb": "62368fc15cefd5807807b209", "generic": "62368fc15cefd5807807b209", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "62368fc15cefd5807807b209", +"motherduck": "62368fc15cefd5807807b209", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "62368fc15cefd5807807b209", "postgresql": "62368fc15cefd5807807b209", "snowflake": "62368fc15cefd5807807b209", @@ -24012,8 +24012,8 @@ "databricks": "38b2ec94bf02be40007ce525", "duckdb": "38b2ec94bf02be40007ce525", "generic": "38b2ec94bf02be40007ce525", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "38b2ec94bf02be40007ce525", +"motherduck": "38b2ec94bf02be40007ce525", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "38b2ec94bf02be40007ce525", "postgresql": "38b2ec94bf02be40007ce525", "snowflake": "38b2ec94bf02be40007ce525", @@ -24023,17 +24023,17 @@ [ "type", { -"DuckDB": "89cc3662f602a13aef39eb80", -"None": "f2bbf59a9c02f59ca89e66b6", -"bigquery": "f2bbf59a9c02f59ca89e66b6", -"databricks": "f2bbf59a9c02f59ca89e66b6", -"duckdb": "89cc3662f602a13aef39eb80", -"generic": "f2bbf59a9c02f59ca89e66b6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "89cc3662f602a13aef39eb80", -"postgresql": "89cc3662f602a13aef39eb80", -"snowflake": "6f88ef12d95c26bf8f0bd9e5", +"DuckDB": "01e3dfc024c37a8d62c7f810", +"None": "3a4d8bca5124769db23a43df", +"bigquery": "3a4d8bca5124769db23a43df", +"databricks": "3a4d8bca5124769db23a43df", +"duckdb": "01e3dfc024c37a8d62c7f810", +"generic": "3a4d8bca5124769db23a43df", +"motherduck": "01e3dfc024c37a8d62c7f810", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "01e3dfc024c37a8d62c7f810", +"postgresql": "01e3dfc024c37a8d62c7f810", +"snowflake": "db03b5ccf153c75e43846a6a", "tsql": "d1dc9a140dbe2eb1874c6ad7" } ], @@ -24046,8 +24046,8 @@ "databricks": "0703bf71a54942bdaa28406e", "duckdb": "0703bf71a54942bdaa28406e", "generic": "0703bf71a54942bdaa28406e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0703bf71a54942bdaa28406e", +"motherduck": "0703bf71a54942bdaa28406e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0703bf71a54942bdaa28406e", "postgresql": "0703bf71a54942bdaa28406e", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -24060,11 +24060,11 @@ "DuckDB": "ef2689646fd9e772e27d2727", "None": "ef2689646fd9e772e27d2727", "bigquery": "ef2689646fd9e772e27d2727", -"databricks": "e63b3aab09c82955e2560928", +"databricks": "ef2689646fd9e772e27d2727", "duckdb": "ef2689646fd9e772e27d2727", "generic": "ef2689646fd9e772e27d2727", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "ef2689646fd9e772e27d2727", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ef2689646fd9e772e27d2727", "postgresql": "ef2689646fd9e772e27d2727", "snowflake": "ef2689646fd9e772e27d2727", @@ -24075,17 +24075,17 @@ "type", { "DuckDB": "c43574432afc90bcba6944b2", -"None": "7040afbb3a088baf55e328b2", -"bigquery": "7040afbb3a088baf55e328b2", -"databricks": "ba611057434e43344935471b", +"None": "61c3e35de716257a33271a64", +"bigquery": "61c3e35de716257a33271a64", +"databricks": "61c3e35de716257a33271a64", "duckdb": "c43574432afc90bcba6944b2", -"generic": "7040afbb3a088baf55e328b2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "1b3243409566f14d81c11fff", -"postgresql": "1b3243409566f14d81c11fff", +"generic": "61c3e35de716257a33271a64", +"motherduck": "c43574432afc90bcba6944b2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "61c3e35de716257a33271a64", +"postgresql": "61c3e35de716257a33271a64", "snowflake": "5b6535982c7d4576e34ee7cb", -"tsql": "d1dc9a140dbe2eb1874c6ad7" +"tsql": "61c3e35de716257a33271a64" } ], [ @@ -24097,8 +24097,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -24114,8 +24114,8 @@ "databricks": "b46dfa976d5ba04f95bea32b", "duckdb": "b46dfa976d5ba04f95bea32b", "generic": "b46dfa976d5ba04f95bea32b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b46dfa976d5ba04f95bea32b", +"motherduck": "b46dfa976d5ba04f95bea32b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "34dcdfe3e3afe6140e1353e0", "postgresql": "34dcdfe3e3afe6140e1353e0", "snowflake": "b46dfa976d5ba04f95bea32b", @@ -24131,8 +24131,8 @@ "databricks": "031c4b14dc8b97fdb38e28b3", "duckdb": "bc9875824f2c0eb90fcb5e51", "generic": "0289c868616e51dc348bba36", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe2c1943dc029130eda3f066", +"motherduck": "bc9875824f2c0eb90fcb5e51", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fe2c1943dc029130eda3f066", "postgresql": "fe2c1943dc029130eda3f066", "snowflake": "ffc35af553194c8ccffc9058", @@ -24148,8 +24148,8 @@ "databricks": "c844c96e1e3ffc72d5f90df9", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "c844c96e1e3ffc72d5f90df9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28eb0d6de9724959ce5ba43e", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c844c96e1e3ffc72d5f90df9", "postgresql": "c844c96e1e3ffc72d5f90df9", "snowflake": "c844c96e1e3ffc72d5f90df9", @@ -24165,8 +24165,8 @@ "databricks": "3dbbe73ee4310c73030e8110", "duckdb": "3dbbe73ee4310c73030e8110", "generic": "3dbbe73ee4310c73030e8110", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3dbbe73ee4310c73030e8110", +"motherduck": "3dbbe73ee4310c73030e8110", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3dbbe73ee4310c73030e8110", "postgresql": "3dbbe73ee4310c73030e8110", "snowflake": "f43dac38f34a1c0618a3f812", @@ -24182,8 +24182,8 @@ "databricks": "04d8552335eed867485d59b8", "duckdb": "8f29d402cac8d3b922d1a4e7", "generic": "04d8552335eed867485d59b8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "8f29d402cac8d3b922d1a4e7", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "8f29d402cac8d3b922d1a4e7", "postgresql": "8f29d402cac8d3b922d1a4e7", "snowflake": "c60d34b521887e82716fd787", @@ -24199,8 +24199,8 @@ "databricks": "d53793d8f8d21c36a117dedb", "duckdb": "d53793d8f8d21c36a117dedb", "generic": "d53793d8f8d21c36a117dedb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d53793d8f8d21c36a117dedb", +"motherduck": "d53793d8f8d21c36a117dedb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d53793d8f8d21c36a117dedb", "postgresql": "d53793d8f8d21c36a117dedb", "snowflake": "d53793d8f8d21c36a117dedb", @@ -24216,8 +24216,8 @@ "databricks": "251aba3fef503ce64556c701", "duckdb": "251aba3fef503ce64556c701", "generic": "251aba3fef503ce64556c701", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "251aba3fef503ce64556c701", +"motherduck": "251aba3fef503ce64556c701", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b7d6fc051a3c1ea6deb29ff4", "postgresql": "b7d6fc051a3c1ea6deb29ff4", "snowflake": "251aba3fef503ce64556c701", @@ -24229,14 +24229,14 @@ { "DuckDB": "df19c722959a4259621a1e09", "None": "df19c722959a4259621a1e09", -"bigquery": "df19c722959a4259621a1e09", +"bigquery": "c7c38fdeaeb7858a44976f1d", "databricks": "df19c722959a4259621a1e09", "duckdb": "df19c722959a4259621a1e09", "generic": "df19c722959a4259621a1e09", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "df19c722959a4259621a1e09", -"postgres": "df19c722959a4259621a1e09", -"postgresql": "df19c722959a4259621a1e09", +"motherduck": "df19c722959a4259621a1e09", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "1d7dd2736322bc33dae77252", +"postgresql": "1d7dd2736322bc33dae77252", "snowflake": "df19c722959a4259621a1e09", "tsql": "df19c722959a4259621a1e09" } @@ -24250,8 +24250,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -24267,8 +24267,8 @@ "databricks": "4662a1758442ce44b830269f", "duckdb": "4662a1758442ce44b830269f", "generic": "4662a1758442ce44b830269f", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4662a1758442ce44b830269f", +"motherduck": "4662a1758442ce44b830269f", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "4662a1758442ce44b830269f", "postgresql": "4662a1758442ce44b830269f", "snowflake": "4662a1758442ce44b830269f", @@ -24284,8 +24284,8 @@ "databricks": "73975a560983dfcdc361f145", "duckdb": "73975a560983dfcdc361f145", "generic": "73975a560983dfcdc361f145", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "73975a560983dfcdc361f145", +"motherduck": "73975a560983dfcdc361f145", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "73975a560983dfcdc361f145", "postgresql": "73975a560983dfcdc361f145", "snowflake": "73975a560983dfcdc361f145", @@ -24301,8 +24301,8 @@ "databricks": "c844c96e1e3ffc72d5f90df9", "duckdb": "8a3f094f18fb4047f0febf59", "generic": "c844c96e1e3ffc72d5f90df9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "28eb0d6de9724959ce5ba43e", +"motherduck": "8a3f094f18fb4047f0febf59", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c844c96e1e3ffc72d5f90df9", "postgresql": "c844c96e1e3ffc72d5f90df9", "snowflake": "c844c96e1e3ffc72d5f90df9", @@ -24318,8 +24318,8 @@ "databricks": "fedd65248cdf63b24b8c1567", "duckdb": "fedd65248cdf63b24b8c1567", "generic": "fedd65248cdf63b24b8c1567", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fedd65248cdf63b24b8c1567", +"motherduck": "fedd65248cdf63b24b8c1567", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fedd65248cdf63b24b8c1567", "postgresql": "fedd65248cdf63b24b8c1567", "snowflake": "fedd65248cdf63b24b8c1567", @@ -24330,17 +24330,17 @@ "type", { "DuckDB": "c50a316d091c821866e4bfba", -"None": "c50a316d091c821866e4bfba", -"bigquery": "c50a316d091c821866e4bfba", -"databricks": "c50a316d091c821866e4bfba", +"None": "e6ffdece2977be55da73b59a", +"bigquery": "e6ffdece2977be55da73b59a", +"databricks": "e6ffdece2977be55da73b59a", "duckdb": "c50a316d091c821866e4bfba", -"generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"generic": "e6ffdece2977be55da73b59a", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", -"tsql": "c50a316d091c821866e4bfba" +"tsql": "e6ffdece2977be55da73b59a" } ], [ @@ -24352,8 +24352,8 @@ "databricks": "d79c3ac70093cb4cb531e71c", "duckdb": "d79c3ac70093cb4cb531e71c", "generic": "d79c3ac70093cb4cb531e71c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d79c3ac70093cb4cb531e71c", +"motherduck": "d79c3ac70093cb4cb531e71c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d79c3ac70093cb4cb531e71c", "postgresql": "d79c3ac70093cb4cb531e71c", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -24369,10 +24369,10 @@ "databricks": "3673b4fbbd8a7fed7120ec2b", "duckdb": "3673b4fbbd8a7fed7120ec2b", "generic": "3673b4fbbd8a7fed7120ec2b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3673b4fbbd8a7fed7120ec2b", -"postgres": "3673b4fbbd8a7fed7120ec2b", -"postgresql": "3673b4fbbd8a7fed7120ec2b", +"motherduck": "3673b4fbbd8a7fed7120ec2b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9aff7fcc7d550e1a8d105471", +"postgresql": "9aff7fcc7d550e1a8d105471", "snowflake": "3673b4fbbd8a7fed7120ec2b", "tsql": "3673b4fbbd8a7fed7120ec2b" } @@ -24381,15 +24381,15 @@ "type", { "DuckDB": "d2e8f9f7288b371199b1652e", -"None": "9bca83e64bfacf0a80ca9b34", +"None": "fe1b4290ba0c2395d949643d", "bigquery": "fe1b4290ba0c2395d949643d", "databricks": "fe1b4290ba0c2395d949643d", "duckdb": "d2e8f9f7288b371199b1652e", -"generic": "9bca83e64bfacf0a80ca9b34", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe1b4290ba0c2395d949643d", -"postgres": "d2e8f9f7288b371199b1652e", -"postgresql": "d2e8f9f7288b371199b1652e", +"generic": "fe1b4290ba0c2395d949643d", +"motherduck": "d2e8f9f7288b371199b1652e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "e0367d4e3fcf904cd5657220", +"postgresql": "e0367d4e3fcf904cd5657220", "snowflake": "c326a07d4a8adffde91e55cd", "tsql": "fe1b4290ba0c2395d949643d" } @@ -24403,8 +24403,8 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "b8321619b794db091c9e96a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b8321619b794db091c9e96a1", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b8321619b794db091c9e96a1", "postgresql": "b8321619b794db091c9e96a1", "snowflake": "b8321619b794db091c9e96a1", @@ -24420,8 +24420,8 @@ "databricks": "77ffdb80029bd6e2cca768ad", "duckdb": "77ffdb80029bd6e2cca768ad", "generic": "77ffdb80029bd6e2cca768ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "77ffdb80029bd6e2cca768ad", +"motherduck": "77ffdb80029bd6e2cca768ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "77ffdb80029bd6e2cca768ad", "postgresql": "77ffdb80029bd6e2cca768ad", "snowflake": "77ffdb80029bd6e2cca768ad", @@ -24433,14 +24433,14 @@ { "DuckDB": "b69a57be16780e0a5e4acadb", "None": "b69a57be16780e0a5e4acadb", -"bigquery": "b69a57be16780e0a5e4acadb", +"bigquery": "67d251c042daee0bb2bf7351", "databricks": "b69a57be16780e0a5e4acadb", "duckdb": "b69a57be16780e0a5e4acadb", "generic": "b69a57be16780e0a5e4acadb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b69a57be16780e0a5e4acadb", -"postgres": "b69a57be16780e0a5e4acadb", -"postgresql": "b69a57be16780e0a5e4acadb", +"motherduck": "b69a57be16780e0a5e4acadb", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a7d1c1767c3d4e900c7d3cb4", +"postgresql": "a7d1c1767c3d4e900c7d3cb4", "snowflake": "b69a57be16780e0a5e4acadb", "tsql": "b69a57be16780e0a5e4acadb" } @@ -24454,8 +24454,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -24471,8 +24471,8 @@ "databricks": "f1625f38cf20f9627bc717a9", "duckdb": "f1625f38cf20f9627bc717a9", "generic": "f1625f38cf20f9627bc717a9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "f1625f38cf20f9627bc717a9", +"motherduck": "f1625f38cf20f9627bc717a9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "f1625f38cf20f9627bc717a9", "postgresql": "f1625f38cf20f9627bc717a9", "snowflake": "f1625f38cf20f9627bc717a9", @@ -24488,8 +24488,8 @@ "databricks": "e998cd8f0f74af0526b75ff0", "duckdb": "e998cd8f0f74af0526b75ff0", "generic": "e998cd8f0f74af0526b75ff0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e998cd8f0f74af0526b75ff0", +"motherduck": "e998cd8f0f74af0526b75ff0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e998cd8f0f74af0526b75ff0", "postgresql": "e998cd8f0f74af0526b75ff0", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -24500,17 +24500,17 @@ "type", { "DuckDB": "fa45b7e4a9ef3b5a9c5781af", -"None": "fa45b7e4a9ef3b5a9c5781af", -"bigquery": "fa45b7e4a9ef3b5a9c5781af", -"databricks": "fa45b7e4a9ef3b5a9c5781af", +"None": "ebe2513b6cb4001e42cc464b", +"bigquery": "0c43dc837a50933667588ef1", +"databricks": "ebe2513b6cb4001e42cc464b", "duckdb": "fa45b7e4a9ef3b5a9c5781af", -"generic": "fa45b7e4a9ef3b5a9c5781af", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fa45b7e4a9ef3b5a9c5781af", +"generic": "ebe2513b6cb4001e42cc464b", +"motherduck": "fa45b7e4a9ef3b5a9c5781af", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fa45b7e4a9ef3b5a9c5781af", "postgresql": "fa45b7e4a9ef3b5a9c5781af", "snowflake": "fa45b7e4a9ef3b5a9c5781af", -"tsql": "fa45b7e4a9ef3b5a9c5781af" +"tsql": "ebe2513b6cb4001e42cc464b" } ], [ @@ -24522,10 +24522,10 @@ "databricks": "4b356542044f1267fcfc487a", "duckdb": "5abf4ec799a2ec32fb10da7e", "generic": "4b356542044f1267fcfc487a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "4b356542044f1267fcfc487a", -"postgres": "4b356542044f1267fcfc487a", -"postgresql": "4b356542044f1267fcfc487a", +"motherduck": "5abf4ec799a2ec32fb10da7e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "a5656d70e208254d1d3e9b4f", +"postgresql": "a5656d70e208254d1d3e9b4f", "snowflake": "4b356542044f1267fcfc487a", "tsql": "4b356542044f1267fcfc487a" } @@ -24534,34 +24534,34 @@ "type", { "DuckDB": "1ebac487198b6c90b027faab", -"None": "1ebac487198b6c90b027faab", -"bigquery": "a428302e4848ffe877284c84", -"databricks": "1ebac487198b6c90b027faab", +"None": "79278352c17c9de0f5a28af5", +"bigquery": "c9b810639795a7bfab91b98d", +"databricks": "79278352c17c9de0f5a28af5", "duckdb": "1ebac487198b6c90b027faab", -"generic": "1ebac487198b6c90b027faab", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1ebac487198b6c90b027faab", -"postgres": "1ebac487198b6c90b027faab", -"postgresql": "1ebac487198b6c90b027faab", -"snowflake": "652ff8b21f638eac8fdc46c5", -"tsql": "c692d974ee3f362ecb38f27c" +"generic": "79278352c17c9de0f5a28af5", +"motherduck": "1ebac487198b6c90b027faab", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "79278352c17c9de0f5a28af5", +"postgresql": "79278352c17c9de0f5a28af5", +"snowflake": "97a20344d811fb77c7690863", +"tsql": "79278352c17c9de0f5a28af5" } ], [ "type", { "DuckDB": "1fe8a2d5a41383bd8e502808", -"None": "1fe8a2d5a41383bd8e502808", +"None": "70f3d045595fbd43219efb36", "bigquery": "70f3d045595fbd43219efb36", "databricks": "70f3d045595fbd43219efb36", "duckdb": "1fe8a2d5a41383bd8e502808", -"generic": "1fe8a2d5a41383bd8e502808", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70f3d045595fbd43219efb36", -"postgres": "1fe8a2d5a41383bd8e502808", -"postgresql": "1fe8a2d5a41383bd8e502808", +"generic": "70f3d045595fbd43219efb36", +"motherduck": "1fe8a2d5a41383bd8e502808", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3c17ab67cd8c034fc4911ef2", +"postgresql": "3c17ab67cd8c034fc4911ef2", "snowflake": "1fe8a2d5a41383bd8e502808", -"tsql": "1fe8a2d5a41383bd8e502808" +"tsql": "70f3d045595fbd43219efb36" } ], [ @@ -24573,8 +24573,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -24587,11 +24587,11 @@ "DuckDB": "035bb58fd2c7dca585a5899c", "None": "035bb58fd2c7dca585a5899c", "bigquery": "035bb58fd2c7dca585a5899c", -"databricks": "958e791772d9007024c889d3", +"databricks": "035bb58fd2c7dca585a5899c", "duckdb": "035bb58fd2c7dca585a5899c", "generic": "035bb58fd2c7dca585a5899c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "770b1d275ab7799c8484f304", +"motherduck": "035bb58fd2c7dca585a5899c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "035bb58fd2c7dca585a5899c", "postgresql": "035bb58fd2c7dca585a5899c", "snowflake": "035bb58fd2c7dca585a5899c", @@ -24602,34 +24602,34 @@ "type", { "DuckDB": "7515c0db413239044adef879", -"None": "7515c0db413239044adef879", -"bigquery": "f276e907770196606577c932", -"databricks": "7515c0db413239044adef879", +"None": "d4b89e61a66aeb076db92954", +"bigquery": "bb92c01c64d1a2d32abb67b5", +"databricks": "d4b89e61a66aeb076db92954", "duckdb": "7515c0db413239044adef879", -"generic": "7515c0db413239044adef879", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7515c0db413239044adef879", +"generic": "d4b89e61a66aeb076db92954", +"motherduck": "7515c0db413239044adef879", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "7515c0db413239044adef879", "postgresql": "7515c0db413239044adef879", "snowflake": "67b5874a2c569226c490211f", -"tsql": "7515c0db413239044adef879" +"tsql": "d4b89e61a66aeb076db92954" } ], [ "type", { "DuckDB": "a3b6b25265be330ec52a3322", -"None": "a3b6b25265be330ec52a3322", -"bigquery": "204206d1d1eecf007966cc5b", -"databricks": "e3bc1b8bad22d8104417b00f", +"None": "62368fc15cefd5807807b209", +"bigquery": "c50a316d091c821866e4bfba", +"databricks": "62368fc15cefd5807807b209", "duckdb": "a3b6b25265be330ec52a3322", -"generic": "a3b6b25265be330ec52a3322", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "919b7ec6a7b49d8527b784cd", -"postgres": "a3b6b25265be330ec52a3322", -"postgresql": "a3b6b25265be330ec52a3322", +"generic": "62368fc15cefd5807807b209", +"motherduck": "a3b6b25265be330ec52a3322", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "62368fc15cefd5807807b209", +"postgresql": "62368fc15cefd5807807b209", "snowflake": "919b7ec6a7b49d8527b784cd", -"tsql": "a3b6b25265be330ec52a3322" +"tsql": "62368fc15cefd5807807b209" } ], [ @@ -24641,8 +24641,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "d61c83ef8bfdf043fa672cb5", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "d61c83ef8bfdf043fa672cb5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -24658,8 +24658,8 @@ "databricks": "268b9b7c7862c9de6569286a", "duckdb": "268b9b7c7862c9de6569286a", "generic": "268b9b7c7862c9de6569286a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "268b9b7c7862c9de6569286a", +"motherduck": "268b9b7c7862c9de6569286a", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "268b9b7c7862c9de6569286a", "postgresql": "268b9b7c7862c9de6569286a", "snowflake": "268b9b7c7862c9de6569286a", @@ -24675,8 +24675,8 @@ "databricks": "b137c998bdaa2555d912e8fa", "duckdb": "27f973a6b2da27a79f5c0074", "generic": "b137c998bdaa2555d912e8fa", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b137c998bdaa2555d912e8fa", +"motherduck": "27f973a6b2da27a79f5c0074", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "b137c998bdaa2555d912e8fa", "postgresql": "b137c998bdaa2555d912e8fa", "snowflake": "b137c998bdaa2555d912e8fa", @@ -24686,16 +24686,16 @@ [ "type", { -"DuckDB": "4ed568ea1adb2c8a7141be20", -"None": "b981394a3f4d961a44e73868", -"bigquery": "f65845aac707b13992e53f87", -"databricks": "b981394a3f4d961a44e73868", -"duckdb": "4ed568ea1adb2c8a7141be20", -"generic": "b981394a3f4d961a44e73868", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", -"postgres": "4ed568ea1adb2c8a7141be20", -"postgresql": "4ed568ea1adb2c8a7141be20", +"DuckDB": "efd6c6e5ae853495e89afda4", +"None": "8e94482f81cd02e976359898", +"bigquery": "8e94482f81cd02e976359898", +"databricks": "8e94482f81cd02e976359898", +"duckdb": "efd6c6e5ae853495e89afda4", +"generic": "8e94482f81cd02e976359898", +"motherduck": "efd6c6e5ae853495e89afda4", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "efd6c6e5ae853495e89afda4", +"postgresql": "efd6c6e5ae853495e89afda4", "snowflake": "311749caf0bd53fee7b23fa1", "tsql": "8e94482f81cd02e976359898" } @@ -24709,8 +24709,8 @@ "databricks": "d5b8a68e96a7848f2aa7b2d6", "duckdb": "d5b8a68e96a7848f2aa7b2d6", "generic": "d5b8a68e96a7848f2aa7b2d6", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d5b8a68e96a7848f2aa7b2d6", +"motherduck": "d5b8a68e96a7848f2aa7b2d6", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d5b8a68e96a7848f2aa7b2d6", "postgresql": "d5b8a68e96a7848f2aa7b2d6", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -24726,8 +24726,8 @@ "databricks": "1fa913156c869f5373066b2b", "duckdb": "1fa913156c869f5373066b2b", "generic": "1fa913156c869f5373066b2b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1fa913156c869f5373066b2b", +"motherduck": "1fa913156c869f5373066b2b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1fa913156c869f5373066b2b", "postgresql": "1fa913156c869f5373066b2b", "snowflake": "1fa913156c869f5373066b2b", @@ -24743,10 +24743,10 @@ "databricks": "3673b4fbbd8a7fed7120ec2b", "duckdb": "3673b4fbbd8a7fed7120ec2b", "generic": "3673b4fbbd8a7fed7120ec2b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3673b4fbbd8a7fed7120ec2b", -"postgres": "3673b4fbbd8a7fed7120ec2b", -"postgresql": "3673b4fbbd8a7fed7120ec2b", +"motherduck": "3673b4fbbd8a7fed7120ec2b", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "9aff7fcc7d550e1a8d105471", +"postgresql": "9aff7fcc7d550e1a8d105471", "snowflake": "3673b4fbbd8a7fed7120ec2b", "tsql": "3673b4fbbd8a7fed7120ec2b" } @@ -24760,10 +24760,10 @@ "databricks": "7770c7aba92f1bba835ef3f8", "duckdb": "7770c7aba92f1bba835ef3f8", "generic": "7770c7aba92f1bba835ef3f8", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7770c7aba92f1bba835ef3f8", -"postgres": "7770c7aba92f1bba835ef3f8", -"postgresql": "7770c7aba92f1bba835ef3f8", +"motherduck": "7770c7aba92f1bba835ef3f8", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "55de7125c9ef4832b853c094", +"postgresql": "55de7125c9ef4832b853c094", "snowflake": "7770c7aba92f1bba835ef3f8", "tsql": "7770c7aba92f1bba835ef3f8" } @@ -24772,17 +24772,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -24794,8 +24794,8 @@ "databricks": "e57ac1541fb51b362cd5b470", "duckdb": "e57ac1541fb51b362cd5b470", "generic": "e57ac1541fb51b362cd5b470", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "e57ac1541fb51b362cd5b470", +"motherduck": "e57ac1541fb51b362cd5b470", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "e57ac1541fb51b362cd5b470", "postgresql": "e57ac1541fb51b362cd5b470", "snowflake": "4b356542044f1267fcfc487a", @@ -24805,18 +24805,18 @@ [ "type", { -"DuckDB": "436bf2624179c536a0157841", -"None": "3c6e55e3040bec9d73dd1bae", +"DuckDB": "4cce9f29790ed5c86ebce81d", +"None": "00e3060d99b1d939ec9f3616", "bigquery": "f1e1c64039c39ec114b5ede6", -"databricks": "21bc62fef8fe5716ed99f7be", -"duckdb": "436bf2624179c536a0157841", -"generic": "3c6e55e3040bec9d73dd1bae", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "3c6e55e3040bec9d73dd1bae", -"postgres": "8141c88dbee0d916c5447a97", -"postgresql": "8141c88dbee0d916c5447a97", +"databricks": "b1961f9482bbd04d61ba1c52", +"duckdb": "4cce9f29790ed5c86ebce81d", +"generic": "00e3060d99b1d939ec9f3616", +"motherduck": "4cce9f29790ed5c86ebce81d", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "6e5449b80c5f377b124fba88", +"postgresql": "6e5449b80c5f377b124fba88", "snowflake": "cfa49b8de6f3939e2701f38d", -"tsql": "28f1e600e17067938d8a74f4" +"tsql": "7755cfc984436641c774f231" } ], [ @@ -24824,12 +24824,12 @@ { "DuckDB": "431ce1fed4f6ce1e6c2a6479", "None": "6282d36888b8e0aadda09779", -"bigquery": "9da8d0485dcb32b3b63f0ed3", +"bigquery": "02e0b2ed25f32a8d09350a23", "databricks": "6282d36888b8e0aadda09779", "duckdb": "431ce1fed4f6ce1e6c2a6479", "generic": "6282d36888b8e0aadda09779", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "431ce1fed4f6ce1e6c2a6479", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "431ce1fed4f6ce1e6c2a6479", "postgresql": "431ce1fed4f6ce1e6c2a6479", "snowflake": "4ea9204c46e0ec4e3a94f672", @@ -24845,8 +24845,8 @@ "databricks": "9917d6c8c04307304904313b", "duckdb": "9917d6c8c04307304904313b", "generic": "9917d6c8c04307304904313b", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "9917d6c8c04307304904313b", +"motherduck": "9917d6c8c04307304904313b", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9917d6c8c04307304904313b", "postgresql": "9917d6c8c04307304904313b", "snowflake": "9917d6c8c04307304904313b", @@ -24862,8 +24862,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -24879,8 +24879,8 @@ "databricks": "0712c33a016f7dd6a37ee011", "duckdb": "0712c33a016f7dd6a37ee011", "generic": "0712c33a016f7dd6a37ee011", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0f3f17e0fe13c04b84ded556", +"motherduck": "0712c33a016f7dd6a37ee011", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0712c33a016f7dd6a37ee011", "postgresql": "0712c33a016f7dd6a37ee011", "snowflake": "0712c33a016f7dd6a37ee011", @@ -24892,14 +24892,14 @@ { "DuckDB": "fe23594c781e79cbb0c8927a", "None": "fe23594c781e79cbb0c8927a", -"bigquery": "fe23594c781e79cbb0c8927a", +"bigquery": "8a3f094f18fb4047f0febf59", "databricks": "fe23594c781e79cbb0c8927a", "duckdb": "fe23594c781e79cbb0c8927a", "generic": "fe23594c781e79cbb0c8927a", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fe23594c781e79cbb0c8927a", -"postgres": "fe23594c781e79cbb0c8927a", -"postgresql": "fe23594c781e79cbb0c8927a", +"motherduck": "fe23594c781e79cbb0c8927a", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "8a3f094f18fb4047f0febf59", +"postgresql": "8a3f094f18fb4047f0febf59", "snowflake": "38b2ec94bf02be40007ce525", "tsql": "fe23594c781e79cbb0c8927a" } @@ -24907,18 +24907,18 @@ [ "type", { -"DuckDB": "7fa8f60977ab1aa305af9d4b", -"None": "8c6ca3f77bf44837655a903e", -"bigquery": "89138ea06a52edf9b7991dd8", -"databricks": "3aa50c38af078e9f02f5a1c8", -"duckdb": "7fa8f60977ab1aa305af9d4b", -"generic": "8c6ca3f77bf44837655a903e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "8c6ca3f77bf44837655a903e", -"postgres": "8c6ca3f77bf44837655a903e", -"postgresql": "8c6ca3f77bf44837655a903e", +"DuckDB": "909ab87c62786e3181c86ba6", +"None": "22bed6e6de77311e5f24d414", +"bigquery": "22bed6e6de77311e5f24d414", +"databricks": "22bed6e6de77311e5f24d414", +"duckdb": "909ab87c62786e3181c86ba6", +"generic": "22bed6e6de77311e5f24d414", +"motherduck": "909ab87c62786e3181c86ba6", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "22bed6e6de77311e5f24d414", +"postgresql": "22bed6e6de77311e5f24d414", "snowflake": "05188197e056965f895710b0", -"tsql": "8c6ca3f77bf44837655a903e" +"tsql": "22bed6e6de77311e5f24d414" } ], [ @@ -24926,14 +24926,14 @@ { "DuckDB": "ee47e1424cb01deb0cf51760", "None": "ee47e1424cb01deb0cf51760", -"bigquery": "ee47e1424cb01deb0cf51760", +"bigquery": "893ff554934dfc6f8253597b", "databricks": "ee47e1424cb01deb0cf51760", "duckdb": "ee47e1424cb01deb0cf51760", "generic": "ee47e1424cb01deb0cf51760", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ee47e1424cb01deb0cf51760", -"postgres": "ee47e1424cb01deb0cf51760", -"postgresql": "ee47e1424cb01deb0cf51760", +"motherduck": "ee47e1424cb01deb0cf51760", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "3c1521654088dd54d0dc9ead", +"postgresql": "3c1521654088dd54d0dc9ead", "snowflake": "ee47e1424cb01deb0cf51760", "tsql": "ee47e1424cb01deb0cf51760" } @@ -24947,10 +24947,10 @@ "databricks": "49fdedaa5356fcccaa6f2f38", "duckdb": "49fdedaa5356fcccaa6f2f38", "generic": "49fdedaa5356fcccaa6f2f38", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "49fdedaa5356fcccaa6f2f38", -"postgres": "49fdedaa5356fcccaa6f2f38", -"postgresql": "49fdedaa5356fcccaa6f2f38", +"motherduck": "49fdedaa5356fcccaa6f2f38", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "cf14fd6412673db81925b3a0", +"postgresql": "cf14fd6412673db81925b3a0", "snowflake": "49fdedaa5356fcccaa6f2f38", "tsql": "49fdedaa5356fcccaa6f2f38" } @@ -24959,17 +24959,17 @@ "type", { "DuckDB": "70fb4728c3adcf6dbe4975d5", -"None": "70fb4728c3adcf6dbe4975d5", -"bigquery": "70fb4728c3adcf6dbe4975d5", -"databricks": "70fb4728c3adcf6dbe4975d5", +"None": "fb4b6f5101f973d16ea8d1f3", +"bigquery": "fb4b6f5101f973d16ea8d1f3", +"databricks": "fb4b6f5101f973d16ea8d1f3", "duckdb": "70fb4728c3adcf6dbe4975d5", -"generic": "70fb4728c3adcf6dbe4975d5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "70fb4728c3adcf6dbe4975d5", +"generic": "fb4b6f5101f973d16ea8d1f3", +"motherduck": "70fb4728c3adcf6dbe4975d5", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "70fb4728c3adcf6dbe4975d5", "postgresql": "70fb4728c3adcf6dbe4975d5", "snowflake": "70fb4728c3adcf6dbe4975d5", -"tsql": "70fb4728c3adcf6dbe4975d5" +"tsql": "fb4b6f5101f973d16ea8d1f3" } ], [ @@ -24981,10 +24981,10 @@ "databricks": "1e75e5227bea0ae294ac7af1", "duckdb": "19bdd92ea0cc5fb028657510", "generic": "b8321619b794db091c9e96a1", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "b8321619b794db091c9e96a1", -"postgres": "b8321619b794db091c9e96a1", -"postgresql": "b8321619b794db091c9e96a1", +"motherduck": "19bdd92ea0cc5fb028657510", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "bce26544873d6ec9191c9a86", +"postgresql": "bce26544873d6ec9191c9a86", "snowflake": "b8321619b794db091c9e96a1", "tsql": "b8321619b794db091c9e96a1" } @@ -24998,8 +24998,8 @@ "databricks": "46e6767a9ba2cc5fbfdc02d0", "duckdb": "9f79ba9960d200f49192fa87", "generic": "46e6767a9ba2cc5fbfdc02d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "6c03a3450ea4742ee7396e3c", +"motherduck": "9f79ba9960d200f49192fa87", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "9f79ba9960d200f49192fa87", "postgresql": "9f79ba9960d200f49192fa87", "snowflake": "72c40a5d469c974c1952c3c2", @@ -25015,8 +25015,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -25032,8 +25032,8 @@ "databricks": "d61c83ef8bfdf043fa672cb5", "duckdb": "305aaf0b95cee629dd46380c", "generic": "d61c83ef8bfdf043fa672cb5", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "d61c83ef8bfdf043fa672cb5", +"motherduck": "305aaf0b95cee629dd46380c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "d61c83ef8bfdf043fa672cb5", "postgresql": "d61c83ef8bfdf043fa672cb5", "snowflake": "97a20344d811fb77c7690863", @@ -25049,8 +25049,8 @@ "databricks": "0dc0164f07551711f23943eb", "duckdb": "0dc0164f07551711f23943eb", "generic": "0dc0164f07551711f23943eb", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0dc0164f07551711f23943eb", +"motherduck": "0dc0164f07551711f23943eb", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0dc0164f07551711f23943eb", "postgresql": "0dc0164f07551711f23943eb", "snowflake": "0dc0164f07551711f23943eb", @@ -25066,8 +25066,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", @@ -25079,12 +25079,12 @@ { "DuckDB": "0f799a95a4f5ef502897f02c", "None": "0f799a95a4f5ef502897f02c", -"bigquery": "b1b23029bedaa16e2a6f8516", +"bigquery": "1e75e5227bea0ae294ac7af1", "databricks": "0f799a95a4f5ef502897f02c", "duckdb": "0f799a95a4f5ef502897f02c", "generic": "0f799a95a4f5ef502897f02c", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0f799a95a4f5ef502897f02c", +"motherduck": "0f799a95a4f5ef502897f02c", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0f799a95a4f5ef502897f02c", "postgresql": "0f799a95a4f5ef502897f02c", "snowflake": "0f799a95a4f5ef502897f02c", @@ -25100,8 +25100,8 @@ "databricks": "2bfa352725f9d8a91250f75e", "duckdb": "2bfa352725f9d8a91250f75e", "generic": "2bfa352725f9d8a91250f75e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2bfa352725f9d8a91250f75e", +"motherduck": "2bfa352725f9d8a91250f75e", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "2bfa352725f9d8a91250f75e", "postgresql": "2bfa352725f9d8a91250f75e", "snowflake": "2bfa352725f9d8a91250f75e", @@ -25113,12 +25113,12 @@ { "DuckDB": "bda0c90e573c6a01b55627e2", "None": "bda0c90e573c6a01b55627e2", -"bigquery": "bda0c90e573c6a01b55627e2", +"bigquery": "0017a88bb60ec3403c786ed8", "databricks": "bda0c90e573c6a01b55627e2", "duckdb": "bda0c90e573c6a01b55627e2", "generic": "bda0c90e573c6a01b55627e2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "bda0c90e573c6a01b55627e2", +"motherduck": "bda0c90e573c6a01b55627e2", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "bda0c90e573c6a01b55627e2", "postgresql": "bda0c90e573c6a01b55627e2", "snowflake": "bda0c90e573c6a01b55627e2", @@ -25134,10 +25134,10 @@ "databricks": "2705f19942a4194a72576911", "duckdb": "2705f19942a4194a72576911", "generic": "2705f19942a4194a72576911", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "2705f19942a4194a72576911", -"postgres": "2705f19942a4194a72576911", -"postgresql": "2705f19942a4194a72576911", +"motherduck": "2705f19942a4194a72576911", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "5226d7e2a62c7650a87d6e84", +"postgresql": "5226d7e2a62c7650a87d6e84", "snowflake": "2705f19942a4194a72576911", "tsql": "2705f19942a4194a72576911" } @@ -25147,12 +25147,12 @@ { "DuckDB": "558da38ec4d9d5d6c72f60cf", "None": "558da38ec4d9d5d6c72f60cf", -"bigquery": "e6ffdece2977be55da73b59a", +"bigquery": "c50a316d091c821866e4bfba", "databricks": "558da38ec4d9d5d6c72f60cf", "duckdb": "558da38ec4d9d5d6c72f60cf", "generic": "558da38ec4d9d5d6c72f60cf", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "558da38ec4d9d5d6c72f60cf", +"motherduck": "558da38ec4d9d5d6c72f60cf", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "558da38ec4d9d5d6c72f60cf", "postgresql": "558da38ec4d9d5d6c72f60cf", "snowflake": "558da38ec4d9d5d6c72f60cf", @@ -25163,17 +25163,17 @@ "type", { "DuckDB": "314ed506f24e92300a425780", -"None": "314ed506f24e92300a425780", -"bigquery": "314ed506f24e92300a425780", -"databricks": "314ed506f24e92300a425780", +"None": "9a66317fb8d040bf991502c0", +"bigquery": "9bafb08d41fd1c208b5deca4", +"databricks": "9a66317fb8d040bf991502c0", "duckdb": "314ed506f24e92300a425780", -"generic": "314ed506f24e92300a425780", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "314ed506f24e92300a425780", +"generic": "9a66317fb8d040bf991502c0", +"motherduck": "314ed506f24e92300a425780", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "314ed506f24e92300a425780", "postgresql": "314ed506f24e92300a425780", "snowflake": "314ed506f24e92300a425780", -"tsql": "314ed506f24e92300a425780" +"tsql": "9a66317fb8d040bf991502c0" } ], [ @@ -25185,8 +25185,8 @@ "databricks": "0b8ad171901cc2b7725617da", "duckdb": "0b8ad171901cc2b7725617da", "generic": "0b8ad171901cc2b7725617da", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0b8ad171901cc2b7725617da", +"motherduck": "0b8ad171901cc2b7725617da", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0b8ad171901cc2b7725617da", "postgresql": "0b8ad171901cc2b7725617da", "snowflake": "0b8ad171901cc2b7725617da", @@ -25197,17 +25197,17 @@ "type", { "DuckDB": "363159f251e264604338e690", -"None": "363159f251e264604338e690", -"bigquery": "363159f251e264604338e690", -"databricks": "363159f251e264604338e690", +"None": "9a63ad6168418af23d9b6fd1", +"bigquery": "8b8d5f75ba6dec68b4cebc6b", +"databricks": "9a63ad6168418af23d9b6fd1", "duckdb": "363159f251e264604338e690", -"generic": "363159f251e264604338e690", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "363159f251e264604338e690", -"postgres": "363159f251e264604338e690", -"postgresql": "363159f251e264604338e690", +"generic": "9a63ad6168418af23d9b6fd1", +"motherduck": "363159f251e264604338e690", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f276e907770196606577c932", +"postgresql": "f276e907770196606577c932", "snowflake": "363159f251e264604338e690", -"tsql": "363159f251e264604338e690" +"tsql": "9a63ad6168418af23d9b6fd1" } ], [ @@ -25219,8 +25219,8 @@ "databricks": "e3bc1b8bad22d8104417b00f", "duckdb": "1d8b945fd793ecdcacb90c21", "generic": "1d8b945fd793ecdcacb90c21", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1d8b945fd793ecdcacb90c21", +"motherduck": "1d8b945fd793ecdcacb90c21", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "3efe8bb57c98fcbe74096b3c", "postgresql": "3efe8bb57c98fcbe74096b3c", "snowflake": "1d8b945fd793ecdcacb90c21", @@ -25236,8 +25236,8 @@ "databricks": "52c81331cf7cf6b56395e239", "duckdb": "52c81331cf7cf6b56395e239", "generic": "52c81331cf7cf6b56395e239", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "52c81331cf7cf6b56395e239", +"motherduck": "52c81331cf7cf6b56395e239", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "52c81331cf7cf6b56395e239", "postgresql": "52c81331cf7cf6b56395e239", "snowflake": "52c81331cf7cf6b56395e239", @@ -25253,8 +25253,8 @@ "databricks": "ebc821a95b9286e22c2b26d0", "duckdb": "ebc821a95b9286e22c2b26d0", "generic": "ebc821a95b9286e22c2b26d0", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "ebc821a95b9286e22c2b26d0", +"motherduck": "ebc821a95b9286e22c2b26d0", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "ebc821a95b9286e22c2b26d0", "postgresql": "ebc821a95b9286e22c2b26d0", "snowflake": "ebc821a95b9286e22c2b26d0", @@ -25270,8 +25270,8 @@ "databricks": "85569b6cabaea71507846025", "duckdb": "85569b6cabaea71507846025", "generic": "85569b6cabaea71507846025", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "85569b6cabaea71507846025", +"motherduck": "85569b6cabaea71507846025", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "85569b6cabaea71507846025", "postgresql": "85569b6cabaea71507846025", "snowflake": "85569b6cabaea71507846025", @@ -25287,10 +25287,10 @@ "databricks": "7f7363467010768b3de8fe05", "duckdb": "7f7363467010768b3de8fe05", "generic": "7f7363467010768b3de8fe05", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "7f7363467010768b3de8fe05", -"postgres": "7f7363467010768b3de8fe05", -"postgresql": "7f7363467010768b3de8fe05", +"motherduck": "7f7363467010768b3de8fe05", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "039da801fa79cb9eabce307d", +"postgresql": "039da801fa79cb9eabce307d", "snowflake": "7f7363467010768b3de8fe05", "tsql": "7f7363467010768b3de8fe05" } @@ -25304,8 +25304,8 @@ "databricks": "cc5643d29bc94cb1374704e9", "duckdb": "cc5643d29bc94cb1374704e9", "generic": "cc5643d29bc94cb1374704e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "cc5643d29bc94cb1374704e9", +"motherduck": "cc5643d29bc94cb1374704e9", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "cc5643d29bc94cb1374704e9", "postgresql": "cc5643d29bc94cb1374704e9", "snowflake": "cc5643d29bc94cb1374704e9", @@ -25321,8 +25321,8 @@ "databricks": "c50a316d091c821866e4bfba", "duckdb": "c50a316d091c821866e4bfba", "generic": "c50a316d091c821866e4bfba", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c50a316d091c821866e4bfba", +"motherduck": "c50a316d091c821866e4bfba", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "c50a316d091c821866e4bfba", "postgresql": "c50a316d091c821866e4bfba", "snowflake": "c50a316d091c821866e4bfba", @@ -25338,10 +25338,10 @@ "databricks": "c91ea1fc2032c6f196132d1e", "duckdb": "c91ea1fc2032c6f196132d1e", "generic": "c91ea1fc2032c6f196132d1e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "c91ea1fc2032c6f196132d1e", -"postgres": "c91ea1fc2032c6f196132d1e", -"postgresql": "c91ea1fc2032c6f196132d1e", +"motherduck": "c91ea1fc2032c6f196132d1e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "652ff8b21f638eac8fdc46c5", +"postgresql": "652ff8b21f638eac8fdc46c5", "snowflake": "c91ea1fc2032c6f196132d1e", "tsql": "c91ea1fc2032c6f196132d1e" } @@ -25352,11 +25352,11 @@ "DuckDB": "87496ad15619ba3d233601ad", "None": "87496ad15619ba3d233601ad", "bigquery": "87496ad15619ba3d233601ad", -"databricks": "5878645b159a4a60df8c2681", +"databricks": "87496ad15619ba3d233601ad", "duckdb": "87496ad15619ba3d233601ad", "generic": "87496ad15619ba3d233601ad", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "675e09f896465856e2673811", +"motherduck": "87496ad15619ba3d233601ad", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "87496ad15619ba3d233601ad", "postgresql": "87496ad15619ba3d233601ad", "snowflake": "87496ad15619ba3d233601ad", @@ -25367,17 +25367,17 @@ "type", { "DuckDB": "23f70cb2333740d79361d52e", -"None": "23f70cb2333740d79361d52e", -"bigquery": "816384049477221b0a4a5b89", -"databricks": "23f70cb2333740d79361d52e", +"None": "a33d60d9b009878ab623c1d8", +"bigquery": "bb92c01c64d1a2d32abb67b5", +"databricks": "a33d60d9b009878ab623c1d8", "duckdb": "23f70cb2333740d79361d52e", -"generic": "23f70cb2333740d79361d52e", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "23f70cb2333740d79361d52e", -"postgres": "23f70cb2333740d79361d52e", -"postgresql": "23f70cb2333740d79361d52e", +"generic": "a33d60d9b009878ab623c1d8", +"motherduck": "23f70cb2333740d79361d52e", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "f276e907770196606577c932", +"postgresql": "f276e907770196606577c932", "snowflake": "23f70cb2333740d79361d52e", -"tsql": "23f70cb2333740d79361d52e" +"tsql": "a33d60d9b009878ab623c1d8" } ], [ @@ -25389,8 +25389,8 @@ "databricks": "1eb7fb4e8cf75773c5573562", "duckdb": "1eb7fb4e8cf75773c5573562", "generic": "1eb7fb4e8cf75773c5573562", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "1eb7fb4e8cf75773c5573562", +"motherduck": "1eb7fb4e8cf75773c5573562", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "1eb7fb4e8cf75773c5573562", "postgresql": "1eb7fb4e8cf75773c5573562", "snowflake": "97a20344d811fb77c7690863", @@ -25406,8 +25406,8 @@ "databricks": "300e2b67af892b463d85f9cd", "duckdb": "300e2b67af892b463d85f9cd", "generic": "300e2b67af892b463d85f9cd", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "300e2b67af892b463d85f9cd", +"motherduck": "300e2b67af892b463d85f9cd", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "300e2b67af892b463d85f9cd", "postgresql": "300e2b67af892b463d85f9cd", "snowflake": "300e2b67af892b463d85f9cd", @@ -25423,10 +25423,10 @@ "databricks": "669084d1ea704ae5b8ee05c2", "duckdb": "669084d1ea704ae5b8ee05c2", "generic": "669084d1ea704ae5b8ee05c2", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "669084d1ea704ae5b8ee05c2", -"postgres": "669084d1ea704ae5b8ee05c2", -"postgresql": "669084d1ea704ae5b8ee05c2", +"motherduck": "669084d1ea704ae5b8ee05c2", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "93c1b5b5960e26479baf88d0", +"postgresql": "93c1b5b5960e26479baf88d0", "snowflake": "669084d1ea704ae5b8ee05c2", "tsql": "669084d1ea704ae5b8ee05c2" } @@ -25436,14 +25436,14 @@ { "DuckDB": "08736485547ba339930697e9", "None": "08736485547ba339930697e9", -"bigquery": "08736485547ba339930697e9", +"bigquery": "837468174ef3e5cb4e4b4070", "databricks": "08736485547ba339930697e9", "duckdb": "08736485547ba339930697e9", "generic": "08736485547ba339930697e9", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "08736485547ba339930697e9", -"postgres": "08736485547ba339930697e9", -"postgresql": "08736485547ba339930697e9", +"motherduck": "08736485547ba339930697e9", +"mysql": "9565c6d3005d7730ba35e40b", +"postgres": "eecd452b4f5f3f4133d0bd0a", +"postgresql": "eecd452b4f5f3f4133d0bd0a", "snowflake": "08736485547ba339930697e9", "tsql": "08736485547ba339930697e9" } @@ -25457,8 +25457,8 @@ "databricks": "0c4610f182f18bf41d306e01", "duckdb": "0c4610f182f18bf41d306e01", "generic": "0c4610f182f18bf41d306e01", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "0c4610f182f18bf41d306e01", +"motherduck": "0c4610f182f18bf41d306e01", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "0c4610f182f18bf41d306e01", "postgresql": "0c4610f182f18bf41d306e01", "snowflake": "0c4610f182f18bf41d306e01", @@ -25474,8 +25474,8 @@ "databricks": "fad6caec1e65529a6edc32df", "duckdb": "fad6caec1e65529a6edc32df", "generic": "fad6caec1e65529a6edc32df", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "fad6caec1e65529a6edc32df", +"motherduck": "fad6caec1e65529a6edc32df", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "fad6caec1e65529a6edc32df", "postgresql": "fad6caec1e65529a6edc32df", "snowflake": "fad6caec1e65529a6edc32df", @@ -25491,8 +25491,8 @@ "databricks": "a428302e4848ffe877284c84", "duckdb": "a428302e4848ffe877284c84", "generic": "a428302e4848ffe877284c84", -"motherduck": "901c5133ed47d4769286e860", -"mysql": "a428302e4848ffe877284c84", +"motherduck": "a428302e4848ffe877284c84", +"mysql": "9565c6d3005d7730ba35e40b", "postgres": "a428302e4848ffe877284c84", "postgresql": "a428302e4848ffe877284c84", "snowflake": "652ff8b21f638eac8fdc46c5", diff --git a/tests/integration/src/sqlbuild/adapter/type_system/_test_types.py b/tests/integration/src/sqlbuild/adapter/type_system/_test_types.py index 858a2dee8e..89beb07f70 100644 --- a/tests/integration/src/sqlbuild/adapter/type_system/_test_types.py +++ b/tests/integration/src/sqlbuild/adapter/type_system/_test_types.py @@ -33,7 +33,7 @@ class TypeNormalizationTestCase: @dataclass(frozen=True) class UnknownDialectTypeTestCase: - """A dialect Polyglot does not know, and the error Python's wheel raised for it.""" + """A dialect SQLBuild has no type rules for, and the error normalization raises for it.""" description: str dialect: str diff --git a/tests/integration/src/sqlbuild/adapter/type_system/helpers.py b/tests/integration/src/sqlbuild/adapter/type_system/helpers.py index fba51cf622..2d019de5d1 100644 --- a/tests/integration/src/sqlbuild/adapter/type_system/helpers.py +++ b/tests/integration/src/sqlbuild/adapter/type_system/helpers.py @@ -168,7 +168,7 @@ def is_normalized(outcome: TypeOutcome) -> bool: def logged_parse_error(outcome: TypeOutcome) -> bool: - """Return whether a Polyglot parse failure was logged for this type.""" + """Return whether a type parse failure was logged for this type.""" return bool(outcome.logged_errors) diff --git a/tests/integration/src/sqlbuild/adapter/type_system/test_native_type_normalization.py b/tests/integration/src/sqlbuild/adapter/type_system/test_native_type_normalization.py index 79cb00a903..bbd2847af9 100644 --- a/tests/integration/src/sqlbuild/adapter/type_system/test_native_type_normalization.py +++ b/tests/integration/src/sqlbuild/adapter/type_system/test_native_type_normalization.py @@ -213,9 +213,9 @@ def test_given_generated_types_when_normalizing_then_every_known_dialect_answers expected_parse_error_logged=True, ), TypeNormalizationTestCase( - description="a dialect outside the old native build", + description="MotherDuck reads types as DuckDB", type_sql="INT", - dialect="mysql", + dialect="motherduck", expected_type=NormalizedType(normalized_name="INT", family=TypeFamily.INTEGER), expected_parse_error_logged=False, ), @@ -244,9 +244,9 @@ def test_given_type_when_normalizing_then_python_shape_is_returned( "test_case", [ UnknownDialectTypeTestCase( - description="a dialect name Polyglot does not know", - dialect="motherduck", - expected_error="ValueError: Unknown dialect: motherduck", + description="a dialect no SQLBuild adapter ships", + dialect="mysql", + expected_error="ValueError: Unknown dialect: mysql", ), ], ids=lambda case: case.description, diff --git a/tests/integration/src/sqlbuild/cli/commands/main/test_rules.py b/tests/integration/src/sqlbuild/cli/commands/main/test_rules.py index 66c39436dc..1ad17c825b 100644 --- a/tests/integration/src/sqlbuild/cli/commands/main/test_rules.py +++ b/tests/integration/src/sqlbuild/cli/commands/main/test_rules.py @@ -2142,10 +2142,16 @@ def test_given_commented_dependency_call_when_running_import_rule_then_comment_i @pytest.mark.parametrize( "test_case", - [RulesIntegrationTestCase("quoted comment marker preserves real call", 1, "SQBRMODEL101")], + [ + RulesIntegrationTestCase( + "quoted comment marker" + ", reported unavailable until SQLBuild parses the dialect", + 0, + "SQBRMODEL101", + ) + ], ids=lambda case: case.description, ) -def test_given_bigquery_quoted_comment_marker_when_running_import_rule_then_real_call_is_detected( +def test_given_bigquery_quoted_comment_marker_when_running_import_rule_then_reports_dialect_unavailable( test_case: RulesIntegrationTestCase, tmp_path: Path, capsys: pytest.CaptureFixture[str], @@ -2194,8 +2200,9 @@ def test_given_bigquery_quoted_comment_marker_when_running_import_rule_then_real payload: dict[str, object] = json.loads(capsys.readouterr().out) findings: list[dict[str, object]] = cast(list[dict[str, object]], payload["findings"]) assert [finding["message"] for finding in findings] == [ - 'import CTE "imported_orders" contains transformation logic' - ] + "SQLBuild cannot parse bigquery SQL for built-in rules yet," + " so these rules were not evaluated: SQBRMODEL101" + ] * 2 @pytest.mark.parametrize( diff --git a/tests/integration/src/sqlbuild/cli/recall/test_recall_cli.py b/tests/integration/src/sqlbuild/cli/recall/test_recall_cli.py index d2def05b76..de785bec9c 100644 --- a/tests/integration/src/sqlbuild/cli/recall/test_recall_cli.py +++ b/tests/integration/src/sqlbuild/cli/recall/test_recall_cli.py @@ -149,6 +149,12 @@ def test_given_a_removed_format_when_querying_then_it_is_a_usage_error_naming_th expected_fragments=("│ order_id │ amount │", "INTEGER", "DECIMAL(10,2)"), unexpected_fragments=("RECORD",), ), + QueryLayoutTestCase( + description="warehouse schema lists SQLBuild's state relations only", + args=("recall", "schema", "--backend", "warehouse"), + expected_fragments=("_sqlbuild_fingerprints", "BASE TABLE"), + unexpected_fragments=("│ orders ",), + ), QueryLayoutTestCase( description="a limited result says how to see more", args=("query", "SELECT range AS order_id FROM range(30)"), diff --git a/tests/integration/src/sqlbuild/compiler/compiled_project/publication/_test_types.py b/tests/integration/src/sqlbuild/compiler/compiled_project/publication/_test_types.py index c90b11f1d8..9c8f2db146 100644 --- a/tests/integration/src/sqlbuild/compiler/compiled_project/publication/_test_types.py +++ b/tests/integration/src/sqlbuild/compiler/compiled_project/publication/_test_types.py @@ -340,3 +340,20 @@ class ChangedJobTestCase(WorkerExecutionTestCase): new_order_sql: str execute: Callable[[Path, str], list[tuple[Any, ...]]] expected_runs: tuple[tuple[str, frozenset[str], dict[str, int]], ...] + + +@dataclass(frozen=True) +class AdhocRunTestCase: + """An ad-hoc command a person checks and launches through `sqb server`, and what follows.""" + + description: str + person: Literal["admin", "developer"] + command: list[str] + expected_permissions: list[list[Any]] + expected_status: str + expected_reason: str + expected_statements: bool + expected_customers_tables: int + job_settings: str = "" + expected_scheduled_status: str = "success" + expected_worker_log: str = "" diff --git a/tests/integration/src/sqlbuild/compiler/compiled_project/publication/helpers.py b/tests/integration/src/sqlbuild/compiler/compiled_project/publication/helpers.py index 8d93e83735..83d2828953 100644 --- a/tests/integration/src/sqlbuild/compiler/compiled_project/publication/helpers.py +++ b/tests/integration/src/sqlbuild/compiler/compiled_project/publication/helpers.py @@ -1352,3 +1352,30 @@ def audit_node_keys(api: SnapshotApi, target: str) -> frozenset[str]: """Keys of the current snapshot's audit nodes.""" nodes: dict[str, Any] = current_snapshot(api, target)["nodes"] return frozenset(filter(lambda key: nodes[key]["kind"] == "audit", nodes)) + + +def worker_statements(control_store: Path, run_id: str) -> int: + """Statements the run's worker invocations recorded, as the run statement read selects them.""" + with sqlite3.connect(control_store) as control: + invocations: list[str] = [ + invocation + for (invocation,) in control.execute( + "SELECT invocation_id FROM invocations WHERE invocation_id LIKE 'worker/%' " + "AND json_extract(external_context, '$.run_id') = ?", + (run_id,), + ).fetchall() + ] + assert invocations, f"run {run_id} has no worker invocation" + return sum( + control.execute( + "SELECT count(*) FROM statements WHERE invocation_id=? OR invocation_id LIKE ?", + (invocation, invocation + "/%"), + ).fetchone()[0] + for invocation in invocations + ) + + +def security_audit_rows(control_store: Path) -> int: + """Rows in the security audit, so a test can show a request recorded nothing.""" + with sqlite3.connect(control_store) as control: + return control.execute("SELECT count(*) FROM sqb_security_audit").fetchone()[0] diff --git a/tests/integration/src/sqlbuild/compiler/compiled_project/publication/test_adhoc.py b/tests/integration/src/sqlbuild/compiler/compiled_project/publication/test_adhoc.py new file mode 100644 index 0000000000..f8054f779d --- /dev/null +++ b/tests/integration/src/sqlbuild/compiler/compiled_project/publication/test_adhoc.py @@ -0,0 +1,187 @@ +"""People check and launch ad-hoc commands through `sqb server`; workers run exactly what was resolved.""" + +from pathlib import Path +from typing import Any +from urllib.error import HTTPError +from uuid import uuid4 + +import pytest + +from tests.integration.src.sqlbuild.compiler.compiled_project.publication._test_types import ( + AdhocRunTestCase, + WorkerExecutionTestCase, +) +from tests.integration.src.sqlbuild.compiler.compiled_project.publication.helpers import ( + duckdb_rows, + scheduled_worker, + security_audit_rows, + wait_for_run, + worker_statements, +) +from tests.integration.src.sqlbuild.compiler.compiled_project.publication.person.helpers import ( + person_login, + person_read, + person_request, + provision_person, +) + +WAREHOUSE_REFUSAL: str = ( + "the requested warehouse is not configured for this target on this location; " + "no warehouse statement ran" +) + + +@pytest.mark.parametrize( + "test_case", + [ + AdhocRunTestCase( + description="a developer builds exactly the resolved selection", + person="developer", + command=["build", "--select", "customers"], + expected_permissions=[["run.adhoc", True]], + expected_status="success", + expected_reason="worker executor completed and closed its connections", + expected_statements=True, + expected_customers_tables=1, + ), + AdhocRunTestCase( + description="an unconfigured warehouse is refused before any statement, " + "while a published job keeps its authored warehouse", + person="admin", + command=["build", "--select", "customers", "--warehouse", "ANALYST_XS"], + expected_permissions=[["run.adhoc", True], ["run.warehouse", True]], + expected_status="failed", + expected_reason=WAREHOUSE_REFUSAL, + expected_statements=False, + expected_customers_tables=0, + # DuckDB has no session warehouse, so the CLI refuses the job's warehouse: the + # published job reached the CLI instead of being refused by the worker. + job_settings="settings={'warehouse': 'ANALYST_XS'}, ", + expected_scheduled_status="failed", + expected_worker_log="error[C260]: --warehouse ANALYST_XS was given", + ), + ], + ids=lambda case: case.description, +) +def test_given_an_ad_hoc_command_when_checked_and_launched_then_the_worker_runs_the_resolved_selection( + test_case: AdhocRunTestCase, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + worker_case: WorkerExecutionTestCase = WorkerExecutionTestCase( + description=test_case.description, + model_sql="SELECT 5 AS amount", + warehouse_query="SELECT amount FROM orders", + expected_status="success", + expected_rows=[(5,)], + files={ + "models/customers.sql": ( + "MODEL (description 'Customers.', materialized table);\nSELECT 1 AS id\n" + ) + } + | { + "orchestration/orders.py": ( + "from sqlbuild.orchestration import job, schedule\n" + 'annual_orders = job("annual_orders", select="orders", ' + f'description="Build orders.", {test_case.job_settings}' + 'schedule=schedule(cron="0 0 * * *", timezone="UTC", ' + 'starts_at="2000-01-01T00:00:00Z"))\n' + ) + }, + ) + with scheduled_worker( + tmp_path, + monkeypatch, + worker_case, + server_arguments=("--password-origin", "https://control.example.com"), + ) as worker: + scheduled: dict[str, Any] = wait_for_run( + worker.api, worker.process, worker.log, job_key="annual_orders" + ) + assert scheduled["status"] == test_case.expected_scheduled_status, worker.log.read_text() + assert scheduled["status_reason"] != WAREHOUSE_REFUSAL, "published jobs are not refused" + assert test_case.expected_worker_log in worker.log.read_text() + admin: dict[str, Any] = provision_person( + tmp_path / "control", "create-user", "orders-admin" + ) + developer: dict[str, Any] = provision_person( + tmp_path / "control", "create-principal", "orders-developer" + ) + sessions: dict[str, tuple[str, str]] = { + "admin": person_login(worker.api, admin["name"]), + "developer": person_login(worker.api, developer["name"]), + } + assert person_request( + worker.api, + "/api/admin/roles", + body={ + "principal_id": developer["principal_id"], + "role": "developer", + "location_id": worker.api.location_id, + "target": "development", + }, + session=sessions["admin"], + ) == {"applied": True} + session: tuple[str, str] = sessions[test_case.person] + scope: dict[str, Any] = {"location_id": worker.api.location_id, "target": "development"} + audited: int = security_audit_rows(worker.control_store) + check: dict[str, Any] = person_request( + worker.api, + "/api/control/runs/adhoc/check", + body=scope | {"command": test_case.command, "priority": 0}, + session=session, + ) + assert (check["selection"], check["write_set"], check["error"], check["launchable"]) == ( + ["model:customers"], + ["model:customers"], + None, + True, + ) + assert [ + [permission["action"], permission["allowed"]] for permission in check["permissions"] + ] == test_case.expected_permissions + assert security_audit_rows(worker.control_store) == audited, "a dry run records nothing" + with pytest.raises(HTTPError) as refused: + person_request( + worker.api, + "/api/control/runs/adhoc", + body=scope + | { + "run_id": str(uuid4()), + "command": ["build", "--vars", "{}"], + "reason": "Try variables", + "priority": 0, + }, + session=session, + ) + assert refused.value.code == 400 + assert "--vars is not available for ad-hoc runs" in refused.value.read().decode() + run_id: str = str(uuid4()) + launch: dict[str, Any] = scope | { + "run_id": run_id, + "command": test_case.command, + "reason": "Refresh customers", + "priority": 0, + } + for expected_applied in [True, False]: + assert person_request( + worker.api, "/api/control/runs/adhoc", body=launch, session=session + ) == {"applied": expected_applied} + completed: dict[str, Any] = wait_for_run( + worker.api, worker.process, worker.log, run_id=run_id + ) + assert completed["status"] == test_case.expected_status, worker.log.read_text() + assert completed["selection_json"] == ["model:customers"] + listed: list[dict[str, Any]] = person_read( + worker.api, + f"/api/control/locations/{worker.api.location_id}/targets/development/runs", + session=session, + ) + assert {run["run_id"]: run["status_reason"] for run in listed}[run_id] == ( + test_case.expected_reason + ), "the person sees why the run ended" + assert (worker_statements(worker.control_store, run_id) > 0) == ( + test_case.expected_statements + ) + assert duckdb_rows( + worker.project / "warehouse.duckdb", + "SELECT count(*) FROM information_schema.tables WHERE table_name='customers'", + ) == [(test_case.expected_customers_tables,)] diff --git a/tests/integration/src/sqlbuild/compiler/contracts/test_native_model_contracts.py b/tests/integration/src/sqlbuild/compiler/contracts/test_native_model_contracts.py index f7e8c93063..3d5658116a 100644 --- a/tests/integration/src/sqlbuild/compiler/contracts/test_native_model_contracts.py +++ b/tests/integration/src/sqlbuild/compiler/contracts/test_native_model_contracts.py @@ -128,9 +128,9 @@ def test_given_generated_contracts_when_validating_then_every_contract_family_is ), ), FormerlyDeferredContractTestCase( - description="a dialect outside the old native build", + description="a shipped dialect without a native type parser", declared_type="INTEGER", - dialect="mysql", + dialect="tsql", expected_diagnostics=( ( "K002", @@ -184,9 +184,9 @@ def test_given_input_python_once_answered_when_validating_then_native_diagnostic "test_case", [ UnknownDialectContractTestCase( - description="a dialect Polyglot does not know raises the wheel's error", - dialect="motherduck", - expected_error="Unknown dialect: motherduck", + description="a dialect no adapter ships raises the wheel's error", + dialect="mysql", + expected_error="Unknown dialect: mysql", ), ], ids=lambda case: case.description, diff --git a/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_metadata_checks.py b/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_metadata_checks.py index de7023ba50..21389df895 100644 --- a/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_metadata_checks.py +++ b/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_metadata_checks.py @@ -56,7 +56,7 @@ seed=20261017, count=4, model_count=10, - dialects=("duckdb", "mysql", "tsql", None, "nonsense"), + dialects=("duckdb", "clickhouse", "tsql", None, "nonsense"), non_ascii=True, golden_prefix="metadata_formerly_deferred", expected_minimum_native=12, diff --git a/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_semantic_checks.py b/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_semantic_checks.py index 1f2c4f7dcb..d7d5d1d6dd 100644 --- a/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_semantic_checks.py +++ b/tests/integration/src/sqlbuild/compiler/semantic_checks/test_native_semantic_checks.py @@ -132,7 +132,7 @@ def test_given_generated_failing_projects_when_completing_natively_then_matches_ seed=20261015, count=2, non_ascii=False, - dialects=("mysql", "tsql", "databricks", "sqlite", "oracle", "trino"), + dialects=("tsql", "databricks", "sqlite", "clickhouse"), expected_outcomes=frozenset({"completed"}), ), FormerlyDeferredSemanticTestCase( diff --git a/tests/integration/src/sqlbuild/executor/scenario/test_capture_types.py b/tests/integration/src/sqlbuild/executor/scenario/test_capture_types.py index fd7dd93469..f60ab5ff05 100644 --- a/tests/integration/src/sqlbuild/executor/scenario/test_capture_types.py +++ b/tests/integration/src/sqlbuild/executor/scenario/test_capture_types.py @@ -32,7 +32,7 @@ ScenarioSnapshotCaptureTypesIntegrationTestCase( description="typed duckdb capture jsonl reloads with manifest schema", expected_local_types={ - "decimal_scale_col": "DECIMAL(12, 2)", + "decimal_scale_col": "DECIMAL(12,2)", "date_col": "DATE", "timestamp_col": "TIMESTAMP", "json_col": "JSON", diff --git a/tests/unit/scripts/release_gate/_helpers/_test_types.py b/tests/unit/scripts/release_gate/_helpers/_test_types.py index a1e9dc6c9e..7e6367661f 100644 --- a/tests/unit/scripts/release_gate/_helpers/_test_types.py +++ b/tests/unit/scripts/release_gate/_helpers/_test_types.py @@ -38,6 +38,9 @@ class VerifyCheckNameTestCase: head_ref: str | None head_repository: str | None expected_name: str + action: str | None = None + label: str | None = None + partial: str | None = None @dataclass(frozen=True) @@ -55,6 +58,7 @@ class VerificationSuitesTestCase: checks: str native_checks: str expected_exit_code: int + environment: tuple[tuple[str, str], ...] = () @dataclass(frozen=True) diff --git a/tests/unit/scripts/release_gate/_helpers/test_gate.py b/tests/unit/scripts/release_gate/_helpers/test_gate.py index 1a291b3b67..cdc8628cab 100644 --- a/tests/unit/scripts/release_gate/_helpers/test_gate.py +++ b/tests/unit/scripts/release_gate/_helpers/test_gate.py @@ -24,7 +24,11 @@ "${{ (github.event_name == 'pull_request'" " && github.event.pull_request.head.repo.full_name == github.repository" " && startsWith(github.event.pull_request.head.ref, 'release-please--branches--'))" - " && 'Release pull request suites' || 'Verify' }}" + " && 'Release pull request suites'" + " || ((github.event.action == 'labeled' || github.event.action == 'unlabeled')" + " && github.event.label.name != 'full-verify') && 'Label change (nothing verified)'" + " || needs.changes.outputs.partial == 'true' && 'Fast core (label full-verify for Verify)'" + " || 'Verify' }}" ) @@ -68,6 +72,42 @@ def test_given_repository_workflows_when_checking_release_gate_then_returns_no_e head_repository="contributor/example-project", expected_name="Verify", ), + VerifyCheckNameTestCase( + description="fast-core pull request reports under another name", + event_name="pull_request", + head_ref="feature/inventory-sync", + head_repository=REPOSITORY, + expected_name="Fast core (label full-verify for Verify)", + action="synchronize", + partial="true", + ), + VerifyCheckNameTestCase( + description="unrelated label reports under another name", + event_name="pull_request", + head_ref="feature/inventory-sync", + head_repository=REPOSITORY, + expected_name="Label change (nothing verified)", + action="labeled", + label="documentation", + ), + VerifyCheckNameTestCase( + description="full-verify label reports Verify", + event_name="pull_request", + head_ref="feature/inventory-sync", + head_repository=REPOSITORY, + expected_name="Verify", + action="labeled", + label="full-verify", + partial="false", + ), + VerifyCheckNameTestCase( + description="unclassified pull request reports Verify, which then fails", + event_name="pull_request", + head_ref="feature/inventory-sync", + head_repository=REPOSITORY, + expected_name="Verify", + action="synchronize", + ), VerifyCheckNameTestCase( description="merge group reports Verify", event_name="merge_group", @@ -93,6 +133,9 @@ def test_given_event_when_naming_repository_verify_job_then_reports_expected_nam repository=REPOSITORY, head_ref=test_case.head_ref, head_repository=test_case.head_repository, + action=test_case.action, + label=test_case.label, + partial=test_case.partial, ) assert verify_check_name(job=read_verify_job(), event=event) == test_case.expected_name @@ -109,6 +152,10 @@ def test_given_event_when_naming_repository_verify_job_then_reports_expected_nam "under .github/workflows/verify.yml.", "A release pull request pushed to after its trusted release checks passed can " "merge under .github/workflows/verify.yml.", + "A feature pull request whose fast core passed without the full matrix it needs " + "can merge under .github/workflows/verify.yml.", + "A feature pull request whose only run followed an unrelated label can merge " + "under .github/workflows/verify.yml.", ), ), GateViolationTestCase( @@ -187,6 +234,79 @@ def test_given_workflows_when_checking_release_gate_then_reports_violations( VerificationSuitesTestCase( "docs-only skips terminate successfully", "false", "skipped", "skipped", 0 ), + VerificationSuitesTestCase( + "full run whose selected suites passed", + "true", + "success", + "success", + 0, + ( + ("PARTIAL", "false"), + ("LABEL_ONLY", "false"), + ("DUCKDB_SELECTED", "true"), + ("RECOVERY_SELECTED", "true"), + ), + ), + VerificationSuitesTestCase( + "full run with a selected suite skipped fails closed", + "true", + "success", + "success", + 1, + ( + ("PARTIAL", "false"), + ("LABEL_ONLY", "false"), + ("RECOVERY_SELECTED", "true"), + ("SCHEDULER_RECOVERY_RESULT", "skipped"), + ), + ), + VerificationSuitesTestCase( + "full run with selected wheels skipped fails closed", + "true", + "success", + "success", + 1, + ( + ("PARTIAL", "false"), + ("LABEL_ONLY", "false"), + ("WHEELS_SELECTED", "true"), + ("WHEELS_RESULT", "skipped"), + ("COMPOSE_RESULT", "skipped"), + ), + ), + VerificationSuitesTestCase( + "fast core passes while the full matrix waits for its label", + "true", + "success", + "success", + 0, + ( + ("PARTIAL", "true"), + ("LABEL_ONLY", "false"), + ("DUCKDB_SELECTED", "true"), + ("E2E_SHARDS_RESULT", "skipped"), + ), + ), + VerificationSuitesTestCase( + "fast core cannot mask a static failure", + "true", + "failure", + "success", + 1, + (("PARTIAL", "true"), ("LABEL_ONLY", "false")), + ), + VerificationSuitesTestCase( + "unrelated label run verifies nothing", + "true", + "skipped", + "skipped", + 0, + ( + ("LABEL_ONLY", "true"), + ("CHANGES_RESULT", "skipped"), + ("LOCKFILE_RESULT", "skipped"), + ), + ), ], ids=lambda case: case.description, ) @@ -202,6 +322,7 @@ def test_given_selected_suites_when_aggregating_then_failures_and_skips_resolve( CHECKS_RESULT=test_case.checks, NATIVE_CHECKS_RESULT=test_case.native_checks, ) + environment.update(dict(test_case.environment)) result: subprocess.CompletedProcess[str] = subprocess.run( ["bash", "-e", "-c", step["run"]], env=os.environ | environment, diff --git a/tests/unit/src/sqlbuild/adapters/sqlserver/_test_types.py b/tests/unit/src/sqlbuild/adapters/sqlserver/_test_types.py index d4b2389d6f..3992e76652 100644 --- a/tests/unit/src/sqlbuild/adapters/sqlserver/_test_types.py +++ b/tests/unit/src/sqlbuild/adapters/sqlserver/_test_types.py @@ -41,6 +41,9 @@ class SqlServerRenderCreateTableAsTestCase: @dataclass(frozen=True) class SqlServerRenderCreateFunctionTestCase: description: str + arguments: tuple[tuple[str, str], ...] + returns: str + body_sql: str expected_statements: tuple[str, ...] @@ -164,3 +167,11 @@ class SqlServerMigrationEventReadTestCase: description: str stored_created_at: object expected_read_suffix: str + + +@dataclass(frozen=True) +class SqlServerSourceExpressionRelationTestCase: + description: str + expression: str + expected_relation: str | None + expected_error_code: str | None diff --git a/tests/unit/src/sqlbuild/adapters/sqlserver/test_client.py b/tests/unit/src/sqlbuild/adapters/sqlserver/test_client.py index a192084a60..42ba596d2d 100644 --- a/tests/unit/src/sqlbuild/adapters/sqlserver/test_client.py +++ b/tests/unit/src/sqlbuild/adapters/sqlserver/test_client.py @@ -7,6 +7,7 @@ from sqlbuild._native import FunctionArgument from sqlbuild.adapter.contract.classes.statement_recorder import StatementRecorder +from sqlbuild.adapter.contract.exceptions import AdapterUserError from sqlbuild.adapter.contract.models import ( ColumnInfo, CursorValue, @@ -33,6 +34,7 @@ SqlServerRenderRenameTestCase, SqlServerRollbackTestCase, SqlServerRowDiffEqualitySqlTestCase, + SqlServerSourceExpressionRelationTestCase, ) from tests.unit.src.sqlbuild.adapters.sqlserver.helpers import FakeSqlServerConnection @@ -185,7 +187,7 @@ def test_given_cursor_type_when_rendering_delete_insert_then_sqlserver_uses_type expected_insert_clause=( "([tenant_id], [order_id], [updated_at], [status]) VALUES " "(__source.[tenant_id], __source.[order_id], __source.[updated_at], " - "__source.[status])" + "__source.[status]);" ), ) ], @@ -419,6 +421,9 @@ def test_given_table_target_when_rendering_create_then_sqlserver_uses_select_int [ SqlServerRenderCreateFunctionTestCase( description="renders scalar SQL function with T-SQL body", + arguments=(("order_status", "NVARCHAR(MAX)"),), + returns="BIT", + body_sql="SELECT order_status = 'completed'", expected_statements=( "CREATE OR ALTER FUNCTION dbo.is_completed_order(@order_status NVARCHAR(MAX))\n" "RETURNS BIT\n" @@ -427,7 +432,25 @@ def test_given_table_target_when_rendering_create_then_sqlserver_uses_select_int " RETURN (CASE WHEN @order_status = 'completed' THEN 1 ELSE 0 END)\n" "END", ), - ) + ), + SqlServerRenderCreateFunctionTestCase( + description="renders a table function as an inline TVF that keeps its SELECT", + arguments=(("p_floor", "FLOAT"),), + returns="table", + body_sql=( + "SELECT id, CASE WHEN amount > p_floor THEN 'high' ELSE 'low' END AS band " + "FROM dbo.orders;" + ), + expected_statements=( + "CREATE OR ALTER FUNCTION dbo.is_completed_order(@p_floor FLOAT)\n" + "RETURNS TABLE\n" + "AS\n" + "RETURN (\n" + "SELECT id, CASE WHEN amount > @p_floor THEN 'high' ELSE 'low' END AS band " + "FROM dbo.orders\n" + ")", + ), + ), ], ids=lambda case: case.description, ) @@ -438,9 +461,11 @@ def test_given_sql_function_when_rendering_create_then_sqlserver_declares_functi statements: tuple[str, ...] = adapter.render_create_function( destination="dbo.is_completed_order", - arguments=(FunctionArgument(name="order_status", type="NVARCHAR(MAX)"),), - returns="BIT", - body_sql="SELECT order_status = 'completed'", + arguments=tuple( + FunctionArgument(name=name, type=type_name) for name, type_name in test_case.arguments + ), + returns=test_case.returns, + body_sql=test_case.body_sql, ) assert statements == test_case.expected_statements @@ -456,7 +481,15 @@ def test_given_sql_function_when_rendering_create_then_sqlserver_declares_functi "IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'analytics') " "EXEC('CREATE SCHEMA [analytics]')" ), - ) + ), + SqlServerRenderCreateSchemaTestCase( + description="doubles quotes in both the lookup and the EXEC string", + schema="sqb'meta]x", + expected_statement=( + "IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'sqb''meta]x') " + "EXEC('CREATE SCHEMA [sqb''meta]]x]')" + ), + ), ], ids=lambda case: case.description, ) @@ -853,3 +886,44 @@ def test_given_cross_schema_table_move_when_moving_then_sqlserver_uses_native_tr assert tuple(event.content for event in statement_recorder.snapshot()) == ( test_case.expected_statements ) + + +@pytest.mark.parametrize( + "test_case", + [ + SqlServerSourceExpressionRelationTestCase( + description="a SELECT becomes a derived table", + expression="SELECT id FROM raw.orders;", + expected_relation="(SELECT id FROM raw.orders)", + expected_error_code=None, + ), + SqlServerSourceExpressionRelationTestCase( + description="a relation whose name starts with with stays a relation", + expression="raw.with_orders", + expected_relation="raw.with_orders", + expected_error_code=None, + ), + SqlServerSourceExpressionRelationTestCase( + description="a WITH query is refused because SQL Server cannot nest it", + expression="WITH x AS (SELECT 1 AS id) SELECT id FROM x", + expected_relation=None, + expected_error_code="A715", + ), + ], + ids=lambda case: case.description, +) +def test_given_source_expression_when_rendering_relation_then_sqlserver_nests_only_what_it_can( + test_case: SqlServerSourceExpressionRelationTestCase, +) -> None: + adapter: SqlServerAdapter = SqlServerAdapter() + + try: + relation: str | None = adapter.render_source_expression_relation( + expression=test_case.expression + ) + error_code: str | None = None + except AdapterUserError as error: + relation = None + error_code = error.code + + assert (relation, error_code) == (test_case.expected_relation, test_case.expected_error_code) diff --git a/tests/unit/src/sqlbuild/executor/scenario/_helpers/_test_types.py b/tests/unit/src/sqlbuild/executor/scenario/_helpers/_test_types.py index bd4f84044e..fb5658e8cb 100644 --- a/tests/unit/src/sqlbuild/executor/scenario/_helpers/_test_types.py +++ b/tests/unit/src/sqlbuild/executor/scenario/_helpers/_test_types.py @@ -145,6 +145,14 @@ class ScenarioSnapshotLocalTypeMappingTestCase: local_type_overrides: dict[str, str] | None = None +@dataclass(frozen=True) +class ScenarioSnapshotLocalTypeRefusalTestCase: + description: str + sql_analysis_dialect: str | None + warehouse_type: str + expected_message: str + + @dataclass(frozen=True) class ExecuteScenarioSnapshotCaptureStepsTestCase: description: str diff --git a/tests/unit/src/sqlbuild/executor/scenario/_helpers/test_capture_columns.py b/tests/unit/src/sqlbuild/executor/scenario/_helpers/test_capture_columns.py index 7f363d0be5..f39bcb92b7 100644 --- a/tests/unit/src/sqlbuild/executor/scenario/_helpers/test_capture_columns.py +++ b/tests/unit/src/sqlbuild/executor/scenario/_helpers/test_capture_columns.py @@ -3,9 +3,11 @@ import duckdb import pytest +from sqlbuild.errors.contracts.exceptions import ExecutorInputError from sqlbuild.executor.scenario._helpers.capture.columns import local_type_for_warehouse_type from tests.unit.src.sqlbuild.executor.scenario._helpers._test_types import ( ScenarioSnapshotLocalTypeMappingTestCase, + ScenarioSnapshotLocalTypeRefusalTestCase, ) @@ -157,10 +159,10 @@ expected_local_type="JSON", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="snowflake variant remains variant", + description="snowflake variant is held as json as sqlquack converts it", sql_analysis_dialect="snowflake", warehouse_type="VARIANT", - expected_local_type="VARIANT", + expected_local_type="JSON", ), ScenarioSnapshotLocalTypeMappingTestCase( description="snowflake geography falls back to varchar", @@ -187,10 +189,10 @@ expected_local_type="BIGINT", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="bigquery integer maps to int", + description="bigquery integer is int64 and maps to bigint", sql_analysis_dialect="bigquery", warehouse_type="INTEGER", - expected_local_type="INT", + expected_local_type="BIGINT", ), ScenarioSnapshotLocalTypeMappingTestCase( description="bigquery boolean maps to boolean", @@ -502,7 +504,7 @@ description="databricks map maps to duckdb map type", sql_analysis_dialect="databricks", warehouse_type="MAP", - expected_local_type="MAP(TEXT, INT)", + expected_local_type="MAP(VARCHAR, INT)", ), ScenarioSnapshotLocalTypeMappingTestCase( description="databricks tinyint maps to tinyint", @@ -514,19 +516,19 @@ description="databricks decimal precision and scale are preserved", sql_analysis_dialect="databricks", warehouse_type="DECIMAL(10,2)", - expected_local_type="DECIMAL(10, 2)", + expected_local_type="DECIMAL(10,2)", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="databricks string maps to text", + description="databricks string maps to varchar", sql_analysis_dialect="databricks", warehouse_type="STRING", - expected_local_type="TEXT", + expected_local_type="VARCHAR", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="databricks varchar length maps to text length", + description="databricks varchar length maps to varchar", sql_analysis_dialect="databricks", warehouse_type="VARCHAR(20)", - expected_local_type="TEXT(20)", + expected_local_type="VARCHAR", ), ScenarioSnapshotLocalTypeMappingTestCase( description="databricks timestamp maps to timestamptz", @@ -568,7 +570,73 @@ description="databricks nested map of arrays maps to duckdb map", sql_analysis_dialect="databricks", warehouse_type="MAP>", - expected_local_type="MAP(TEXT, INT[])", + expected_local_type="MAP(VARCHAR, INT[])", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="databricks struct nests lists, maps and quoted field names", + sql_analysis_dialect="databricks", + warehouse_type="STRUCT>, `b c`: TINYINT>", + expected_local_type='STRUCT(a MAP(VARCHAR, BIGINT)[], "b c" TINYINT)', + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server datetime2 is a timestamp", + sql_analysis_dialect="tsql", + warehouse_type="datetime2", + expected_local_type="TIMESTAMP", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server smalldatetime is a timestamp", + sql_analysis_dialect="tsql", + warehouse_type="smalldatetime", + expected_local_type="TIMESTAMP", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server datetimeoffset is a timestamp with time zone", + sql_analysis_dialect="tsql", + warehouse_type="datetimeoffset", + expected_local_type="TIMESTAMPTZ", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server uniqueidentifier is a uuid", + sql_analysis_dialect="tsql", + warehouse_type="uniqueidentifier", + expected_local_type="UUID", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server bit is a boolean", + sql_analysis_dialect="tsql", + warehouse_type="bit", + expected_local_type="BOOLEAN", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server float is eight bytes", + sql_analysis_dialect="tsql", + warehouse_type="float", + expected_local_type="DOUBLE", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server real is four bytes", + sql_analysis_dialect="tsql", + warehouse_type="real", + expected_local_type="FLOAT", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server money is an exact four-place decimal", + sql_analysis_dialect="tsql", + warehouse_type="money", + expected_local_type="DECIMAL(19,4)", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="databricks float keeps four bytes, nested too", + sql_analysis_dialect="databricks", + warehouse_type="ARRAY", + expected_local_type="FLOAT[]", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="generic float keeps four bytes", + sql_analysis_dialect="generic", + warehouse_type="FLOAT", + expected_local_type="FLOAT", ), ScenarioSnapshotLocalTypeMappingTestCase( description="databricks interval year is preserved", @@ -619,16 +687,16 @@ expected_local_type="VARCHAR", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="duckdb struct is preserved with normalized inner type", + description="duckdb struct passes through unchanged", sql_analysis_dialect="duckdb", warehouse_type="STRUCT(a INTEGER)", - expected_local_type="STRUCT(a INT)", + expected_local_type="STRUCT(a INTEGER)", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="duckdb union is preserved with normalized inner types", + description="duckdb union passes through unchanged", sql_analysis_dialect="duckdb", warehouse_type="UNION(num INTEGER, str VARCHAR)", - expected_local_type="UNION(num INT, str TEXT)", + expected_local_type="UNION(num INTEGER, str VARCHAR)", ), ScenarioSnapshotLocalTypeMappingTestCase( description="duckdb list angle syntax maps to bracket list syntax", @@ -643,10 +711,10 @@ expected_local_type="ENUM('a', 'b')", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="duckdb time with timezone maps to timetz", + description="duckdb time with time zone passes through unchanged", sql_analysis_dialect="duckdb", warehouse_type="TIME WITH TIME ZONE", - expected_local_type="TIMETZ", + expected_local_type="TIME WITH TIME ZONE", ), ScenarioSnapshotLocalTypeMappingTestCase( description="unknown type falls back to varchar", @@ -855,10 +923,10 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d expected_local_type="JSON", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="snowflake variant remains variant", + description="snowflake variant is held as json as sqlquack converts it", sql_analysis_dialect="snowflake", warehouse_type="VARIANT", - expected_local_type="VARIANT", + expected_local_type="JSON", ), ScenarioSnapshotLocalTypeMappingTestCase( description="snowflake geography falls back to varchar", @@ -885,10 +953,10 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d expected_local_type="BIGINT", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="bigquery integer maps to int", + description="bigquery integer is int64 and maps to bigint", sql_analysis_dialect="bigquery", warehouse_type="INTEGER", - expected_local_type="INT", + expected_local_type="BIGINT", ), ScenarioSnapshotLocalTypeMappingTestCase( description="bigquery boolean maps to boolean", @@ -1200,7 +1268,7 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d description="databricks map maps to duckdb map type", sql_analysis_dialect="databricks", warehouse_type="MAP", - expected_local_type="MAP(TEXT, INT)", + expected_local_type="MAP(VARCHAR, INT)", ), ScenarioSnapshotLocalTypeMappingTestCase( description="databricks tinyint maps to tinyint", @@ -1212,19 +1280,19 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d description="databricks decimal precision and scale are preserved", sql_analysis_dialect="databricks", warehouse_type="DECIMAL(10,2)", - expected_local_type="DECIMAL(10, 2)", + expected_local_type="DECIMAL(10,2)", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="databricks string maps to text", + description="databricks string maps to varchar", sql_analysis_dialect="databricks", warehouse_type="STRING", - expected_local_type="TEXT", + expected_local_type="VARCHAR", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="databricks varchar length maps to text length", + description="databricks varchar length maps to varchar", sql_analysis_dialect="databricks", warehouse_type="VARCHAR(20)", - expected_local_type="TEXT(20)", + expected_local_type="VARCHAR", ), ScenarioSnapshotLocalTypeMappingTestCase( description="databricks timestamp maps to timestamptz", @@ -1266,7 +1334,73 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d description="databricks nested map of arrays maps to duckdb map", sql_analysis_dialect="databricks", warehouse_type="MAP>", - expected_local_type="MAP(TEXT, INT[])", + expected_local_type="MAP(VARCHAR, INT[])", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="databricks struct nests lists, maps and quoted field names", + sql_analysis_dialect="databricks", + warehouse_type="STRUCT>, `b c`: TINYINT>", + expected_local_type='STRUCT(a MAP(VARCHAR, BIGINT)[], "b c" TINYINT)', + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server datetime2 is a timestamp", + sql_analysis_dialect="tsql", + warehouse_type="datetime2", + expected_local_type="TIMESTAMP", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server smalldatetime is a timestamp", + sql_analysis_dialect="tsql", + warehouse_type="smalldatetime", + expected_local_type="TIMESTAMP", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server datetimeoffset is a timestamp with time zone", + sql_analysis_dialect="tsql", + warehouse_type="datetimeoffset", + expected_local_type="TIMESTAMPTZ", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server uniqueidentifier is a uuid", + sql_analysis_dialect="tsql", + warehouse_type="uniqueidentifier", + expected_local_type="UUID", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server bit is a boolean", + sql_analysis_dialect="tsql", + warehouse_type="bit", + expected_local_type="BOOLEAN", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server float is eight bytes", + sql_analysis_dialect="tsql", + warehouse_type="float", + expected_local_type="DOUBLE", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server real is four bytes", + sql_analysis_dialect="tsql", + warehouse_type="real", + expected_local_type="FLOAT", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="sql server money is an exact four-place decimal", + sql_analysis_dialect="tsql", + warehouse_type="money", + expected_local_type="DECIMAL(19,4)", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="databricks float keeps four bytes, nested too", + sql_analysis_dialect="databricks", + warehouse_type="ARRAY", + expected_local_type="FLOAT[]", + ), + ScenarioSnapshotLocalTypeMappingTestCase( + description="generic float keeps four bytes", + sql_analysis_dialect="generic", + warehouse_type="FLOAT", + expected_local_type="FLOAT", ), ScenarioSnapshotLocalTypeMappingTestCase( description="databricks interval year is preserved", @@ -1317,16 +1451,16 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d expected_local_type="VARCHAR", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="duckdb struct is preserved with normalized inner type", + description="duckdb struct passes through unchanged", sql_analysis_dialect="duckdb", warehouse_type="STRUCT(a INTEGER)", - expected_local_type="STRUCT(a INT)", + expected_local_type="STRUCT(a INTEGER)", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="duckdb union is preserved with normalized inner types", + description="duckdb union passes through unchanged", sql_analysis_dialect="duckdb", warehouse_type="UNION(num INTEGER, str VARCHAR)", - expected_local_type="UNION(num INT, str TEXT)", + expected_local_type="UNION(num INTEGER, str VARCHAR)", ), ScenarioSnapshotLocalTypeMappingTestCase( description="duckdb list angle syntax maps to bracket list syntax", @@ -1341,10 +1475,10 @@ def test_given_warehouse_type_when_mapping_to_local_type_then_returns_expected_d expected_local_type="ENUM('a', 'b')", ), ScenarioSnapshotLocalTypeMappingTestCase( - description="duckdb time with timezone maps to timetz", + description="duckdb time with time zone passes through unchanged", sql_analysis_dialect="duckdb", warehouse_type="TIME WITH TIME ZONE", - expected_local_type="TIMETZ", + expected_local_type="TIME WITH TIME ZONE", ), ScenarioSnapshotLocalTypeMappingTestCase( description="unknown type falls back to varchar", @@ -1405,3 +1539,33 @@ def test_given_mapped_local_type_when_creating_duckdb_table_then_type_is_accepte assert local_type == test_case.expected_local_type connection.execute(f'CREATE TABLE local_type_probe ("value" {local_type})') + + +@pytest.mark.parametrize( + "test_case", + [ + ScenarioSnapshotLocalTypeRefusalTestCase( + description="databricks array of an unmapped element type is refused", + sql_analysis_dialect="databricks", + warehouse_type="ARRAY", + expected_message="SQLBuild cannot map 'GEOGRAPHY' inside it", + ), + ScenarioSnapshotLocalTypeRefusalTestCase( + description="databricks map missing its value type is refused", + sql_analysis_dialect="databricks", + warehouse_type="MAP", + expected_message="SQLBuild cannot map 'MAP' inside it", + ), + ], + ids=lambda case: case.description, +) +def test_given_unmapped_nested_type_when_mapping_to_local_type_then_it_is_refused( + test_case: ScenarioSnapshotLocalTypeRefusalTestCase, +) -> None: + with pytest.raises(ExecutorInputError) as raised: + local_type_for_warehouse_type( + warehouse_type=test_case.warehouse_type, + sql_analysis_dialect=test_case.sql_analysis_dialect, + ) + + assert test_case.expected_message in raised.value.message diff --git a/tests/unit/src/sqlbuild/executor/scenario/main/test_capture.py b/tests/unit/src/sqlbuild/executor/scenario/main/test_capture.py index da2be93081..2e450b4c68 100644 --- a/tests/unit/src/sqlbuild/executor/scenario/main/test_capture.py +++ b/tests/unit/src/sqlbuild/executor/scenario/main/test_capture.py @@ -137,7 +137,7 @@ def render_qualified_name( ScenarioSnapshotColumn( name="customer_id", warehouse_type="DECIMAL(38,0)", - local_type="DECIMAL(38, 0)", + local_type="DECIMAL(38,0)", ), ), ), @@ -151,7 +151,7 @@ def render_qualified_name( ScenarioSnapshotColumn( name="country_code", warehouse_type="VARCHAR", - local_type="TEXT", + local_type="VARCHAR", ), ), ), @@ -170,7 +170,7 @@ def render_qualified_name( ScenarioSnapshotColumn( name="amount", warehouse_type="DECIMAL(10,2)", - local_type="DECIMAL(10, 2)", + local_type="DECIMAL(10,2)", ), ), ), diff --git a/ui/scripts/extract-command-table.mjs b/ui/scripts/extract-command-table.mjs index 7164ee94ab..07ff797501 100644 --- a/ui/scripts/extract-command-table.mjs +++ b/ui/scripts/extract-command-table.mjs @@ -1,14 +1,13 @@ #!/usr/bin/env node // Extracts the run panel's verbs and flags from the CLI's command table, so the -// panel offers exactly the flags `sqb` accepts. +// panel offers exactly the flags an ad-hoc run accepts. // // node scripts/extract-command-table.mjs # rewrite the generated file // node scripts/extract-command-table.mjs --check # fail if it is out of date // -// Flags the server controls or that only make sense on a local shell (target, -// output formats, project paths, dbt interop paths) are left out. Every name in -// the lists below must exist in the table, so a renamed flag fails the script -// instead of silently drifting. +// The verbs and accepted destinations come from the CLI's ad-hoc allowlist, and +// the permissions a flag needs come from the control plane's rules, so the panel +// cannot offer a flag the server refuses or hide a gate it applies. import { readFileSync, writeFileSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; @@ -16,86 +15,62 @@ import { fileURLToPath } from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const SOURCE = resolve(here, '../../crates/sqlbuild-cli/src/argv/command_table.json'); +const ALLOWLIST = resolve(here, '../../crates/sqlbuild-cli/src/adhoc/constants.rs'); +const GATES = resolve(here, '../../crates/sqlbuild-orchestration/src/runs/_helpers/requests/permissions.rs'); const OUTPUT = resolve(here, '../src/lib/run/command-table.generated.json'); -/** Verbs a server run can launch, with the panel's one-line descriptions. */ -const VERBS = { +/** The panel's one-line description of each verb the allowlist accepts. */ +const VERB_DESCRIPTIONS = { build: 'Build the selected nodes, running their tests and audits', test: 'Run unit and scenario tests for the selection', seed: 'Load seed files', - load: 'Run loaders into their source tables', audit: 'Evaluate audits against what is already built', freshness: 'Check source freshness' }; -/** Set by the server from the location's target, the run token and the event stream. */ -const SERVER_CONTROLLED = [ - '--help', - '--target', - '--json', - '--json-output', - '--event-output', - '--project-dir', - '--no-color', - '--debug', - '--manifest', - '--select-file', - '--selection-diagnostics', - '--dbt-project-dir', - '--dbt-profiles-dir', - '--dbt-target', - '--dbt-target-path', - '--since', - '--explain', - '--inspect' -]; - -/** Flags that rewrite history and need `run.rewrite_history`. */ -const REWRITES_HISTORY = [ - '--full-refresh', - '--allow-snapshot-full-refresh', - '--reload', - '--start-cursor', - '--end-cursor', - '--start-cursor-ts', - '--end-cursor-ts', - '--start-cursor-int', - '--end-cursor-int' -]; +/** Flags the planner refuses although their destination is accepted: `--no-load` shares `--load`'s. */ +const REFUSED_FLAGS = ['--load']; /** Descriptions for flags the table leaves undocumented. */ const DESCRIPTIONS = { - '--select': 'Selector expression, e.g. +fact_orders, tag:finance, group:marts', + '--select': 'Selector expression, e.g. +fact_orders, tag:finance, path:models/marts/', '--exclude': 'Selector expression to leave out', - '--defer-to': 'Read unselected upstream nodes from another target', - '--defer-clone-from': 'Clone unselected upstream nodes from another target', - '--defer-sources-to': 'Read sources from another target', '--no-python': 'Skip Python nodes', '--no-tests': 'Skip unit tests during the build', '--no-audits': 'Skip audits during the build', '--max-microbatches': 'Process at most this many microbatches per incremental model', - '--load': 'Run loaders as part of the build', '--no-load': 'Skip loaders', - '--reload': 'Reload loader sources from the beginning', '--fail-fast': 'Stop at the first failed node', '--full-refresh': 'Rebuild incremental models and snapshots from scratch', '--allow-snapshot-full-refresh': 'Allow a full refresh of snapshot models', '--allow-table-type-downgrade': 'Allow replacing a table with a less durable table type', '--allow-retention-decrease': 'Allow lowering a table retention setting', - '--allow-missing-migration-origin': 'Allow a migration whose origin relation no longer exists', '--allow-snapshot-schema-change': 'Allow a schema change on snapshot models', '--concurrency': 'Nodes to run in parallel within this run', '--verbose': 'More detailed output', - '--vars': 'Project variables as a JSON or key=value mapping', '--warehouse': 'Warehouse for this run, overriding the target default', - '--no-sql-analysis': 'Disable SQL syntax, binding and type validation', - '--no-cache': 'Bypass the compile analysis cache', '--case': 'Run one named parameter case', '--state': 'Record freshness results in the state tables', '--fail-on-error': 'Fail when a freshness check errors', '--fail-on-stale': 'Fail when a source is stale' }; +/** The quoted strings of a Rust `const NAME: [...] = [...];` array, which must exist. */ +function rustStrings(path, name) { + const source = readFileSync(path, 'utf8'); + const match = source.match(new RegExp(`const ${name}: \\[[^\\]]+\\] = \\[([\\s\\S]*?)\\];`)); + if (!match) throw new Error(`${name} is not a string array in ${path}`); + return [...match[1].matchAll(/"([^"]+)"/g)].map((m) => m[1]); +} + +const ADHOC_VERBS = rustStrings(ALLOWLIST, 'ADHOC_VERBS'); +const ACCEPTED = new Set([...rustStrings(ALLOWLIST, 'ALLOWED_DESTS'), ...rustStrings(ALLOWLIST, 'SELECTOR_DESTS')]); +/** Destinations whose setting needs a permission beyond run.adhoc, as the control plane decides it. */ +const REQUIRES = new Map([ + ...['full_refresh', ...rustStrings(GATES, 'CURSOR_SETTINGS'), ...rustStrings(GATES, 'ADHOC_DESTRUCTIVE_SETTINGS')].map((dest) => [dest, 'run.rewrite_history']), + ['warehouse', 'run.warehouse'] +]); + function valueType(action) { const flag = action.options[0]; if (action.kind === 'store_true' || action.kind === 'store_false') return 'bool'; @@ -108,7 +83,9 @@ function valueType(action) { function extract(table) { const parsers = table.actions.find((a) => a.kind === 'parsers')?.parsers ?? []; const known = new Set(); - const verbs = Object.entries(VERBS).map(([name, description]) => { + const verbs = ADHOC_VERBS.map((name) => { + const description = VERB_DESCRIPTIONS[name]; + if (!description) throw new Error(`Verb ${name} has no panel description`); const parser = parsers.find((p) => p.name === name); if (!parser) throw new Error(`Verb ${name} is not in the command table`); const flags = []; @@ -116,7 +93,7 @@ function extract(table) { if (!action.options) continue; for (const option of action.options) known.add(option); const flag = action.options.find((o) => o.startsWith('--')); - if (!flag || action.options.some((o) => SERVER_CONTROLLED.includes(o))) continue; + if (!flag || !ACCEPTED.has(action.dest) || REFUSED_FLAGS.includes(flag)) continue; const help = typeof action.help === 'string' ? action.help : null; if (action.help && typeof action.help === 'object') continue; flags.push({ @@ -125,14 +102,17 @@ function extract(table) { type: valueType(action), choices: action.choices ?? null, repeatable: action.kind === 'extend' || action.kind === 'append', - rewritesHistory: REWRITES_HISTORY.includes(flag), + requires: REQUIRES.get(action.dest) ?? null, description: help ?? DESCRIPTIONS[flag] ?? '' }); } return { name, description, flags }; }); - for (const flag of [...SERVER_CONTROLLED, ...REWRITES_HISTORY, ...Object.keys(DESCRIPTIONS)]) { - if (!known.has(flag)) throw new Error(`${flag} is listed here but not in the command table for any launchable verb`); + for (const flag of [...REFUSED_FLAGS, ...Object.keys(DESCRIPTIONS)]) { + if (!known.has(flag)) throw new Error(`${flag} is described here but not in the command table for any ad-hoc verb`); + } + for (const dest of REQUIRES.keys()) { + if (!ACCEPTED.has(dest)) throw new Error(`${dest} is gated by the control plane but not accepted by the ad-hoc allowlist`); } return { source: 'crates/sqlbuild-cli/src/argv/command_table.json', verbs }; } diff --git a/ui/src/lib/api/client.ts b/ui/src/lib/api/client.ts index 11d6581757..e0fb906e31 100644 --- a/ui/src/lib/api/client.ts +++ b/ui/src/lib/api/client.ts @@ -122,7 +122,8 @@ export interface ApiClient extends PlatformApi { listPlatformEvents(query: { activeOnly: boolean }): Promise; - getWarehouseSync(scope: Scope): Promise; + /** Null when the location has reported no recent sync. */ + getWarehouseSync(scope: Scope): Promise; /** Live "changed" signals (SSE in the server client); consumers refetch. */ changes: ChangeSubscription; diff --git a/ui/src/lib/api/http-audit.test.ts b/ui/src/lib/api/http-audit.test.ts new file mode 100644 index 0000000000..7ebccefbf4 --- /dev/null +++ b/ui/src/lib/api/http-audit.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest'; +import { auditMatches, toAuditEntry } from './http-audit'; + +const base = { + storage_id: 7, + event_id: 'e-1', + occurred_at: '2026-11-08T10:00:00Z', + request_id: 'r-1', + ip_address: '127.0.0.1', + before_json: null +}; + +describe('toAuditEntry', () => { + it('lists a denial under the refused action with its decision and reason', () => { + const entry = toAuditEntry({ + ...base, + actor: 'principal:p-2', + actor_name: 'orders-viewer', + action: 'authorization.denied', + granting_role: 'none', + scope_json: { action: 'run.cancel', location_id: 'loc-1', target: 'prod', nodes: [] }, + after_json: { decision_id: 'd-9', allowed: false, reason: 'missing permission' } + }); + expect(entry).toMatchObject({ + actor: { kind: 'user', name: 'orders-viewer' }, + action: 'run.cancel', + outcome: 'denied', + grantingRole: null, + decisionId: 'd-9', + summary: 'missing permission', + after: null, + scope: { locationId: 'loc-1:prod', subject: null } + }); + }); + + it('keeps an allowed change with its role, values and remaining scope', () => { + const entry = toAuditEntry({ + ...base, + actor: 'scheduler/s-1', + action: 'token.issued', + granting_role: 'scheduler', + scope_json: { kind: 'run', run_id: 'run-4' }, + after_json: { token_id: 't-1' } + }); + expect(entry).toMatchObject({ + actor: { kind: 'system', name: 'scheduler/s-1' }, + action: 'token.issued', + outcome: 'allowed', + grantingRole: 'scheduler', + decisionId: null, + after: '{"token_id":"t-1"}', + scope: { locationId: null, subject: 'run_id=run-4' } + }); + expect(auditMatches(entry, { outcome: 'denied' })).toBe(false); + expect(auditMatches(entry, { actor: 'scheduler/s-1', action: 'token.issued' })).toBe(true); + }); +}); diff --git a/ui/src/lib/api/http-audit.ts b/ui/src/lib/api/http-audit.ts new file mode 100644 index 0000000000..0000c6d0d3 --- /dev/null +++ b/ui/src/lib/api/http-audit.ts @@ -0,0 +1,66 @@ +import type { components } from '$control/schema.d.ts'; +import { locationKey } from './http-client'; +import type { AuditEntry, AuditQuery } from './types/platform'; + +type Wire = components['schemas']; + +const DENIED = 'authorization.denied'; + +function fieldsOf(value: unknown): Record { + return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : {}; +} + +function textOf(value: unknown): string | null { + return typeof value === 'string' && value !== '' ? value : null; +} + +function jsonText(value: unknown): string | null { + return value === null || value === undefined ? null : JSON.stringify(value); +} + +/** The scope's location in UI terms, and its remaining fields as the subject. */ +function scopeOf(scope: Record): AuditEntry['scope'] { + const { location_id: location, target, action: _action, kind: _kind, ...rest } = scope; + const locationId = textOf(location); + const subject = Object.entries(rest) + .filter(([, value]) => value !== null && !(Array.isArray(value) && value.length === 0)) + .map(([key, value]) => `${key}=${typeof value === 'string' ? value : JSON.stringify(value)}`) + .join(' '); + return { + locationId: locationId && textOf(target) ? locationKey(locationId, target as string) : locationId, + subject: subject || null + }; +} + +/** A denial is listed under the action it refused, with its decision ID and reason. */ +export function toAuditEntry(wire: Wire['SecurityAuditEvent']): AuditEntry { + const denied = wire.action === DENIED; + const scope = fieldsOf(wire.scope_json); + const after = fieldsOf(wire.after_json); + const name = wire.actor_name ?? wire.actor; + return { + id: wire.event_id, + at: wire.occurred_at, + actor: { kind: wire.actor_name ? 'user' : 'system', name, displayName: name }, + action: denied ? (textOf(scope.action) ?? DENIED) : wire.action, + outcome: denied ? 'denied' : 'allowed', + scope: scopeOf(scope), + grantingRole: denied ? null : wire.granting_role, + decisionId: denied ? textOf(after.decision_id) : null, + evidenceRead: wire.action === 'evidence.read', + summary: denied ? (textOf(after.reason) ?? '') : '', + before: jsonText(wire.before_json), + after: denied ? null : jsonText(wire.after_json), + ip: wire.ip_address ?? '', + requestId: wire.request_id + }; +} + +export function auditMatches(entry: AuditEntry, query: AuditQuery): boolean { + return ( + (!query.actor || entry.actor.name === query.actor) && + (!query.action || entry.action === query.action) && + (!query.outcome || entry.outcome === query.outcome) && + (!query.evidenceOnly || entry.evidenceRead) + ); +} diff --git a/ui/src/lib/api/http-catalog.test.ts b/ui/src/lib/api/http-catalog.test.ts index 12323431a7..d8c3d60150 100644 --- a/ui/src/lib/api/http-catalog.test.ts +++ b/ui/src/lib/api/http-catalog.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import type { components } from '$control/schema.d.ts'; -import { findNode, nodeDetailOf, toCatalogNodes } from './http-catalog'; +import { findNode, nodeDetailOf, nodeJobsOf, toCatalogNodes, toNodeRuns } from './http-catalog'; import { toCheck } from './http-checks'; type W = components['schemas']; @@ -97,3 +97,38 @@ describe('catalog node detail', () => { expect(withSql.sql).toEqual({ raw: 'select {{ x }}', compiled: 'select {{ x }}' }); }); }); + +describe('catalog node runs and jobs', () => { + const snapshot = '01a1b000-0000-7000-8000-000000000001'; + const job = (key: string, kind: 'job' | 'group', selection: string[]): W['JobRecord'] => ({ job_key: key, kind, description: key, selection, concurrency_groups: [], snapshot_id: snapshot }); + const trigger = (id: string, jobKey: string) => ({ trigger_id: id, job_key: jobKey }) as W['TriggerRecord']; + const run = (id: string, status: string, extra: Partial = {}): W['NodeRunRecord'] => ({ run_id: id, job_key: 'orders', queued_at: '2026-11-08T05:59:00Z', status, ...extra }); + + it('lists the jobs and groups that select the node with their own triggers only', () => { + const jobs = [job('orders', 'job', ['model:orders']), job('nightly', 'group', ['model:orders', 'model:customers']), job('customers', 'job', ['model:customers'])]; + const jobsOfOrders = nodeJobsOf('model:orders', jobs, [trigger('t1', 'orders'), trigger('t2', 'customers'), trigger('t3', 'orders')]); + expect(jobsOfOrders).toEqual([ + { key: 'orders', kind: 'job', triggerIds: ['t1', 't3'] }, + { key: 'nightly', kind: 'group', triggerIds: [] } + ]); + }); + + it('keeps finished builds newest first with duration and the reason a node failed or was skipped', () => { + const runs = toNodeRuns([ + run('r4', 'running', { started_at: '2026-11-08T06:00:00Z' }), + run('r3', 'skipped', { skipped_because_of: 'model:stg_orders' }), + run('r2', 'failed', { started_at: '2026-11-08T05:00:00Z', ended_at: '2026-11-08T05:00:01.5Z', error_code: 'warehouse_error' }), + run('r1', 'success', { job_key: null, started_at: '2026-11-08T04:00:00Z', ended_at: '2026-11-08T04:00:30Z', rows_affected: 12 }), + run('r0', 'cancelled') + ]); + expect(runs.map((r) => [r.runId, r.outcome, r.jobKey, r.durationMs, r.rows, r.message])).toEqual([ + ['r3', 'skipped', 'orders', null, null, 'Skipped because stg_orders did not succeed'], + ['r2', 'failed', 'orders', 1500, null, 'warehouse_error'], + ['r1', 'success', null, 30000, 12, null] + ]); + expect(runs[0].startedAt).toBe('2026-11-08T05:59:00Z'); + const nodes = toCatalogNodes([node('model:orders', { materialization: 'table' })]); + const d = nodeDetailOf(nodes, nodes[0], null, null, [], { runs, jobs: [] }); + expect([d.recentOutcomes, d.lastDurationMs, d.jobs]).toEqual([['success', 'failed', 'skipped'], 1500, []]); + }); +}); diff --git a/ui/src/lib/api/http-catalog.ts b/ui/src/lib/api/http-catalog.ts index 5c9c492fbd..d7afd7683b 100644 --- a/ui/src/lib/api/http-catalog.ts +++ b/ui/src/lib/api/http-catalog.ts @@ -1,6 +1,6 @@ import type { components } from '$control/schema.d.ts'; import { shortName } from '$lib/build-area/labels'; -import type { CatalogNode, CatalogNodeDetail, Check, CheckCounts, StaleReason } from './types/build'; +import type { CatalogNode, CatalogNodeDetail, Check, CheckCounts, NodeJob, NodeOutcome, NodeRunEntry, StaleReason } from './types/build'; import type { NodeKind } from './types/operate'; type Wire = components['schemas']; @@ -73,12 +73,49 @@ export function findNode(nodes: CatalogNode[], ref: string): CatalogNode | null return nodes.find((n) => n.key === ref) ?? nodes.find((n) => n.key === `model:${ref}`) ?? nodes.find((n) => shortName(n.key) === ref) ?? null; } +/** Jobs and groups whose resolved selection includes the node, with the triggers that start each. */ +export function nodeJobsOf(nodeKey: string, jobs: Wire['JobRecord'][], triggers: Wire['TriggerRecord'][]): NodeJob[] { + return jobs + .filter((job) => job.selection.includes(nodeKey)) + .map((job) => ({ key: job.job_key, kind: job.kind === 'group' ? 'group' : 'job', triggerIds: triggers.filter((t) => t.job_key === job.job_key).map((t) => t.trigger_id) })); +} + +const FINISHED: readonly NodeOutcome[] = ['success', 'warn', 'failed', 'skipped']; + +/** Finished builds of the node by scheduler runs, newest first; queued, running and cancelled attempts are left out. */ +export function toNodeRuns(wire: Wire['NodeRunRecord'][]): NodeRunEntry[] { + return wire.flatMap((r) => { + const outcome = FINISHED.find((o) => o === r.status); + if (!outcome) return []; + const started = r.started_at ?? r.queued_at; + return [ + { + runId: r.run_id, + fingerprintRunId: null, + jobKey: r.job_key ?? null, + outcome, + startedAt: started, + durationMs: r.started_at && r.ended_at ? Date.parse(r.ended_at) - Date.parse(r.started_at) : null, + rows: r.rows_affected ?? null, + message: r.skipped_because_of ? `Skipped because ${shortName(r.skipped_because_of)} did not succeed` : (r.error_code ?? null) + } + ]; + }); +} + /** - * A listed node with its status and SQL reads and its checks. The server keeps no - * per-node run history, row counts, cost, jobs or migrations, so those stay empty or null and the - * pages hide them. `sql` is null when the person may not see SQL. + * A listed node with its status, SQL, run history, jobs and checks. The server keeps no row + * counts, cost or migrations per node, so those stay empty or null and the pages hide them. `sql` + * is null when the person may not see SQL; `runs` and `jobs` are null when their reads failed. */ -export function nodeDetailOf(nodes: CatalogNode[], node: CatalogNode, status: Wire['NodeStatus'] | null, sql: Wire['NodeSql'] | null, checks: Check[]): CatalogNodeDetail { +export function nodeDetailOf( + nodes: CatalogNode[], + node: CatalogNode, + status: Wire['NodeStatus'] | null, + sql: Wire['NodeSql'] | null, + checks: Check[], + { runs, jobs }: { runs: NodeRunEntry[] | null; jobs: NodeJob[] | null } = { runs: null, jobs: null } +): CatalogNodeDetail { // Checks name their node without its kind, so the attachment tells a source from a model of the same name. const own = checks.filter((c) => c.node === node.key || `${c.attachment}:${c.node}` === node.key); const physical = status?.physical.status === 'observed' ? status.physical.observation.record : null; @@ -87,17 +124,17 @@ export function nodeDetailOf(nodes: CatalogNode[], node: CatalogNode, status: Wi stale: staleOf(status), checks: checkCountsOf(own), downstream: nodes.filter((n) => n.upstream.includes(node.key)).map((n) => n.key), - recentOutcomes: [], + recentOutcomes: (runs ?? []).slice(0, 40).map((r) => r.outcome).reverse(), lastRows: null, rowsDelta: null, - lastDurationMs: null, + lastDurationMs: runs?.find((r) => r.durationMs !== null)?.durationMs ?? null, cost: null, columns: (physical?.columns ?? []).map((c) => ({ name: c.name, type: c.data_type, description: '', audits: [] })), sql: sql?.raw_sql ? { raw: sql.raw_sql, compiled: sql.compiled_sql ?? sql.raw_sql } : null, code: null, builds: [], - jobs: null, - runs: null, + jobs, + runs, checkResults: own, warehouse: null, microbatch: null, diff --git a/ui/src/lib/api/http-client.test.ts b/ui/src/lib/api/http-client.test.ts index 2105944003..4e0ca36b56 100644 --- a/ui/src/lib/api/http-client.test.ts +++ b/ui/src/lib/api/http-client.test.ts @@ -238,6 +238,94 @@ describe('feedAllowed', () => { }); }); +describe('listing requests', () => { + const tick = { tick_id: 'k-2', trigger_id: 't-orders', scheduled_for: '2026-11-07T06:00:00Z', evaluated_at: '2026-11-07T06:00:02Z', epoch: 1, outcome: 'requested', run_id: null, covered_by_run_id: null }; + const declaration = { description: 'Daily orders', job_key: 'orders', schedule: { kind: 'cron', expression: '0 6 * * *', timezone: 'UTC' } }; + const triggers = [ + { trigger_id: 't-orders', name: 'orders_daily', job_key: 'orders', definition_json: declaration, status: 'running', last_tick_for: null, next_due_at: null, latest_tick: tick }, + { trigger_id: 't-stock', name: 'stock_daily', job_key: 'stock', definition_json: declaration, status: 'running', last_tick_for: null, next_due_at: null, latest_tick: null } + ]; + + function server() { + const calls: string[] = []; + vi.stubGlobal('document', { cookie: '' }); + vi.stubGlobal( + 'fetch', + vi.fn(async (request: Request) => { + const path = new URL(request.url).pathname.replace('/api/control/locations/0191f0aa-orders/targets/development', ''); + calls.push(`${request.method} ${path}`); + const body = request.method === 'POST' ? { applied: true } : path === '/triggers' ? triggers : []; + return new Response(JSON.stringify(body), { status: 200, headers: { 'content-type': 'application/json' } }); + }) + ); + return { calls, client: createHttpClient({ baseUrl: 'http://ui.test', onUnauthorized: () => {} }) }; + } + + it('lists triggers with their newest tick in one request, never one per trigger', async () => { + const { calls, client } = server(); + const listed = await client.listTriggers(ORDERS); + expect(listed.map((t) => t.lastTick?.id ?? null)).toEqual(['k-2', null]); + expect(calls.sort()).toEqual(['GET /runs', 'GET /triggers']); + }); + + it('shares listings between pages that refresh together, and reads again after a write', async () => { + const { calls, client } = server(); + await Promise.all([client.listJobs(ORDERS), client.listTriggers(ORDERS)]); + expect(calls.sort()).toEqual(['GET /jobs', 'GET /runs', 'GET /triggers']); + calls.length = 0; + await client.setTriggerStates(ORDERS, ['t-orders'], 'paused'); + expect([calls[0], ...calls.slice(1).sort()]).toEqual(['POST /api/control/triggers', 'GET /runs', 'GET /triggers']); + }); + + it('finds an ad-hoc run it launched while an older runs listing was still in flight', async () => { + let launched: string | null = null; + let releaseOlderListing = () => {}; + const olderListingHeld = new Promise((release) => (releaseOlderListing = release)); + vi.stubGlobal('document', { cookie: '' }); + vi.stubGlobal( + 'fetch', + vi.fn(async (request: Request) => { + if (request.method === 'POST') { + launched = (await request.json()).run_id; + releaseOlderListing(); + return Response.json({ run_id: launched }, { status: 201 }); + } + // The listing answers with the runs queued when it was asked; the first is held until the launch is queued. + const runs = launched ? [queuedAdhocRun(launched)] : []; + if (!launched) await olderListingHeld; + return Response.json(new URL(request.url).pathname.endsWith('/runs') ? runs : []); + }) + ); + const client = createHttpClient({ baseUrl: 'http://ui.test', onUnauthorized: () => {} }); + const olderListing = client.listRuns(ORDERS); + const run = await client.launchAdhocRun(ORDERS, { command: ['build', '--select', 'orders'], priority: 0 }); + expect(run.id).toBe(launched); + expect(await olderListing).toEqual([]); + }); +}); + +function queuedAdhocRun(id: string) { + return { + run_id: id, + root_run_id: id, + kind: 'adhoc', + origin: 'manual', + status: 'queued', + job_key: null, + last_event_storage_id: 1, + location_id: '0191f0aa-orders', + node_counts_json: {}, + priority: 0, + queued_at: '2026-11-08T10:00:00Z', + requested_by: 'principal:p', + requester_name: 'Ada Admin', + rewrite_history: false, + selection_json: ['model.orders'], + snapshot_id: '0191f0dd-0000-7000-8000-000000000000', + target: 'development' + }; +} + describe('getCatalogNode', () => { /** Answers the reads a node page makes; anything else, such as node status, is refused. */ function serve(actions: string[], sqlStatus = 200) { diff --git a/ui/src/lib/api/http-client.ts b/ui/src/lib/api/http-client.ts index 952caa2133..d23ef1f429 100644 --- a/ui/src/lib/api/http-client.ts +++ b/ui/src/lib/api/http-client.ts @@ -2,14 +2,20 @@ import createFetch from 'openapi-fetch'; import type { components, paths } from '$control/schema.d.ts'; import { ApiError, type ApiClient } from './client'; import { checkDetailOf, toCheck, toCheckEvidence, toCheckSql } from './http-checks'; -import { findNode, nodeDetailOf, toCatalogNodes } from './http-catalog'; +import { findNode, nodeDetailOf, toCatalogNodes, nodeJobsOf, toNodeRuns } from './http-catalog'; +import { auditMatches, toAuditEntry } from './http-audit'; import { toSnapshotChanges } from './http-changes'; +import { toLocationSummary, warehouseSyncOf } from './http-locations'; +import { toAccount, toRoleAssignments } from './http-people'; +import { toPlatformEvents } from './http-problems'; +import { roleDetailOf, toRoleDefinition } from './http-roles'; import { jobDetailOf, toJob, toTick, toTrigger, triggerDetailOf } from './http-jobs'; -import { RUN_WINDOW, runDetailOf, runRowPage, runsMatching, runTabCountsOf, toRunNode, toRuns } from './http-runs'; +import { RUN_WINDOW, toAdhocCheck, runDetailOf, runRowPage, runsMatching, runTabCountsOf, toRunNode, toRuns, toRunEvent, toRunOutput, toRunStatements } from './http-runs'; import type { ChangeSubscription, ChangedChannel, ChangedSignal, ConnectionState } from './signals'; -import type { AuthMode, Bootstrap, Capability, Location, Scope } from './types'; +import type { AuthMode, Bootstrap, Capability, Location, PlatformEvent, Scope, WarehouseSync } from './types'; +import type { Account, AuditEntry, AuditQuery, LocationSummary, RoleAssignment, RoleDefinition, RoleDetail } from './types/platform'; import type { CatalogNode, CatalogNodeDetail, Check, CheckDetail, CheckEvidence, CheckSql, SnapshotChange } from './types/build'; -import type { Job, JobDetail, RerunMode, Run, RunDetail, RunListQuery, RunRowPage, RunRowQuery, RunTabCounts, Trigger, TriggerDetail, TriggerState } from './types/operate'; +import type { AdhocRunCheck, AdhocRunRequest, Job, JobDetail, RerunMode, SnapshotNode, Run, RunDetail, RunListQuery, RunRowPage, RunRowQuery, RunTabCounts, Trigger, TriggerDetail, TriggerState, RunEventPage, RunOutputLine, RunStatement, StatementSql } from './types/operate'; type Wire = components['schemas']; @@ -23,6 +29,10 @@ const SUPPORTED: ReadonlySet = new Set([ 'listRuns', 'getRun', 'getRunDetail', + 'listRunEvents', + 'listRunStatements', + 'getStatementSql', + 'listRunOutput', 'cancelRun', 'rerunRun', 'listJobs', @@ -39,9 +49,24 @@ const SUPPORTED: ReadonlySet = new Set([ 'getCheckEvidence', 'listCatalogNodes', 'getCatalogNode', - 'listSnapshotChanges' + 'listSnapshotChanges', + 'listAuditEntries', + 'listAccounts', + 'listRoleAssignments', + 'listLocations', + 'listPlatformEvents', + 'listRoles', + 'getRole', + 'getWarehouseSync', + 'listSnapshotNodes', + 'checkAdhocRun', + 'launchAdhocRun' ]); +/** The server's largest audit page, and how many the audit log reads before it stops looking. */ +const AUDIT_PAGE = 100; +const AUDIT_PAGES = 10; + /** A UI location is one server location and target; its id carries both. */ export function locationKey(locationId: string, target: string): string { return `${locationId}:${target}`; @@ -191,6 +216,9 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt // nodes there whose SQL was refused; each refused read is audited, so neither is asked again until reload. let sqlLocations = new Set(); const sqlRefused = new Set(); + // The same for run output, which needs view.evidence on every node the run selected. + let evidenceLocations = new Set(); + const outputRefused = new Set(); const implemented = { supports: (method: keyof ApiClient) => SUPPORTED.has(method), changes: new HttpChangeFeed(baseUrl, (scope) => feedAllowed(baseUrl, scope, onUnauthorized)), @@ -200,6 +228,7 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt if (!data) throw new ApiError(errorText(error, response), response.status); const bootstrap = toBootstrap(data); sqlLocations = new Set(bootstrap.locations.filter((l) => l.capabilities?.includes('view.sql')).map((l) => l.id)); + evidenceLocations = new Set(bootstrap.locations.filter((l) => l.capabilities?.includes('view.evidence')).map((l) => l.id)); return bootstrap; }, listRunRows: async (scope: Scope, query: RunRowQuery = {}): Promise => runRowPage(await listedRuns(scope), scope.locationId, query), @@ -213,9 +242,46 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt const nodes = (await Promise.all(owners.map(async (owner) => (await runNodes(scope, owner.id)).map((n) => toRunNode(n, owner))))).flat(); return runDetailOf(run, wire, listed, nodes); }, + async listRunEvents(scope: Scope, runId: string, query: { after?: number; limit?: number } = {}): Promise { + const limit = query.limit ?? 1000; + const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/events', { + params: { path: runPath(scope, runId), query: { after: query.after, limit } } + }); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return { events: data.flatMap((e) => toRunEvent(e, runId) ?? []), nextCursor: data.length === limit ? (data.at(-1)?.storage_id ?? null) : null }; + }, + async listRunStatements(scope: Scope, runId: string): Promise { + const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements', { params: { path: runPath(scope, runId) } }); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return toRunStatements(data, runId); + }, + async getStatementSql(scope: Scope, runId: string, statementId: string): Promise { + const refusal = `${scope.locationId}|${runId}`; + if (!sqlLocations.has(scope.locationId) || sqlRefused.has(refusal)) throw new ApiError('You may not view the SQL of this run', 403); + const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/statements/sql', { + params: { path: runPath(scope, runId), query: { statement_id: statementId } } + }); + if (response.status === 401) onUnauthorized(); + if (response.status === 403) sqlRefused.add(refusal); + if (!data) throw new ApiError(errorText(error, response), response.status); + return { statementId: data.statement_id, sql: data.sql ?? null }; + }, + async listRunOutput(scope: Scope, runId: string): Promise { + const refusal = `${scope.locationId}|${runId}`; + if (!evidenceLocations.has(scope.locationId) || outputRefused.has(refusal)) return []; + const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/runs/{run_id}/output', { params: { path: runPath(scope, runId) } }); + if (response.status === 401) onUnauthorized(); + if (response.status === 403) outputRefused.add(refusal); + if (response.status === 403) return []; + if (!data) throw new ApiError(errorText(error, response), response.status); + return toRunOutput(data); + }, async cancelRun(scope: Scope, runId: string): Promise { const body = { run_id: runId, event_id: crypto.randomUUID(), reason: 'Cancelled from the web UI' }; const { error, response } = await http.POST('/api/control/runs/cancel', { body, params: { header: { 'x-sqb-csrf': csrfToken(document.cookie) } } }); + inFlight.clear(); if (response.status === 401) onUnauthorized(); if (!response.ok) throw new ApiError(errorText(error, response), response.status); return requireRun(scope, runId); @@ -224,6 +290,7 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt const source = await requireRun(scope, runId); const body = { run_id: crypto.randomUUID(), source_run_id: runId, from_failure: mode === 'from_failure', priority: source.priority, reason: 'Rerun from the web UI' }; const { error, response } = await http.POST('/api/control/runs/rerun', { body, params: { header: { 'x-sqb-csrf': csrfToken(document.cookie) } } }); + inFlight.clear(); if (response.status === 401) onUnauthorized(); if (!response.ok) throw new ApiError(errorText(error, response), response.status); return requireRun(scope, body.run_id); @@ -242,6 +309,7 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt const { locationId, target } = parseLocationKey(scope.locationId); const body = { run_id: crypto.randomUUID(), location_id: locationId, target, job_key: key, priority: 0, reason: 'Started from the web UI' }; const { error, response } = await http.POST('/api/control/runs', { body, params: { header: { 'x-sqb-csrf': csrfToken(document.cookie) } } }); + inFlight.clear(); if (response.status === 401) onUnauthorized(); if (!response.ok) throw new ApiError(errorText(error, response), response.status); return requireRun(scope, body.run_id); @@ -253,6 +321,27 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt if (!data) throw new ApiError(errorText(error, response), response.status); return toCatalogNodes(data); }, + async listSnapshotNodes(scope: Scope): Promise { + const nodes = await implemented.listCatalogNodes(scope); + return nodes.map((n) => ({ key: n.key, kind: n.kind, group: n.group ?? '', tags: n.tags, path: n.path, cursor: null, upstream: n.upstream })); + }, + async checkAdhocRun(scope: Scope, request: AdhocRunRequest): Promise { + const { locationId, target } = parseLocationKey(scope.locationId); + const body = { location_id: locationId, target, command: request.command, priority: request.priority }; + const { data, error, response } = await http.POST('/api/control/runs/adhoc/check', { body, params: { header: { 'x-sqb-csrf': csrfToken(document.cookie) } } }); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return toAdhocCheck(data); + }, + async launchAdhocRun(scope: Scope, request: AdhocRunRequest): Promise { + const { locationId, target } = parseLocationKey(scope.locationId); + const body = { run_id: crypto.randomUUID(), location_id: locationId, target, command: request.command, priority: request.priority, reason: 'Started from the web UI' }; + const { error, response } = await http.POST('/api/control/runs/adhoc', { body, params: { header: { 'x-sqb-csrf': csrfToken(document.cookie) } } }); + inFlight.clear(); + if (response.status === 401) onUnauthorized(); + if (!response.ok) throw new ApiError(errorText(error, response), response.status); + return requireRun(scope, body.run_id); + }, async listSnapshotChanges(scope: Scope, query: { limit?: number } = {}): Promise { const { locationId, target } = parseLocationKey(scope.locationId); const [snapshots, runs] = await Promise.all([ @@ -263,23 +352,87 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt if (!snapshots.data) throw new ApiError(errorText(snapshots.error, snapshots.response), snapshots.response.status); return toSnapshotChanges(snapshots.data, runs); }, + /** Newest first, reading at most `AUDIT_PAGES` server pages to find `limit` matching entries. */ + async listAuditEntries(query: AuditQuery = {}): Promise { + const limit = query.limit ?? 100; + const found: AuditEntry[] = []; + let before = Number.MAX_SAFE_INTEGER; + for (let page = 0; page < AUDIT_PAGES && found.length < limit; page++) { + const { data, error, response } = await http.GET('/api/control/audit', { params: { query: { before, limit: AUDIT_PAGE } } }); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + found.push(...data.map(toAuditEntry).filter((entry) => auditMatches(entry, query))); + if (data.length < AUDIT_PAGE) break; + before = data[data.length - 1].storage_id; + } + return found.slice(0, limit); + }, + async getWarehouseSync(scope: Scope): Promise { + const { locationId, target } = parseLocationKey(scope.locationId); + const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/health', { params: { path: { location_id: locationId, target } } }); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return warehouseSyncOf(data); + }, + async listPlatformEvents(): Promise { + const { data, error, response } = await http.GET('/api/control/platform/problems'); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return toPlatformEvents(data, Date.now()); + }, + /** Each location the person sees, with health only where they may list the whole target. */ + async listLocations(): Promise { + const { data, error, response } = await http.GET('/api/control/bootstrap'); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return Promise.all( + toBootstrap(data).locations.map(async (location) => { + const { locationId, target } = parseLocationKey(location.id); + const path = { location_id: locationId, target }; + const [health, snapshots] = await Promise.all([ + http.GET('/api/control/locations/{location_id}/targets/{target}/health', { params: { path } }), + http.GET('/api/control/locations/{location_id}/targets/{target}/snapshots', { params: { path, query: { limit: 1 } } }) + ]); + if (health.response.status === 401 || snapshots.response.status === 401) onUnauthorized(); + return toLocationSummary(location, health.data ?? null, snapshots.data?.[0] ?? null, Date.now()); + }) + ); + }, + listAccounts: async (): Promise => (await people()).map(toAccount), + listRoles: async (locationId: string): Promise => (await roleOverviews(locationId)).map(toRoleDefinition), + async getRole(locationId: string, name: string): Promise { + const [roles, assignments] = await Promise.all([roleOverviews(locationId), implemented.listRoleAssignments()]); + const role = roles.find((r) => r.key === name); + return role ? roleDetailOf(role, assignments, locationId) : null; + }, + async listRoleAssignments(): Promise { + const { data, error, response } = await http.GET('/api/admin/roles'); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + // Group members come from the people read; without it, SSO-derived roles stay unlisted. + return toRoleAssignments(data, await people().catch(() => [])); + }, async getCatalogNode(scope: Scope, ref: string): Promise { const nodes = await implemented.listCatalogNodes(scope); const node = findNode(nodes, ref); if (!node) return null; const { locationId, target } = parseLocationKey(scope.locationId); const path = { location_id: locationId, target }; - const [status, sql, checks] = await Promise.all([ + const [status, sql, checks, runs, jobs] = await Promise.all([ http.GET('/api/control/locations/{location_id}/targets/{target}/node-status', { params: { path, query: { node_key: node.key } } }), sqlLocations.has(scope.locationId) && !sqlRefused.has(`${scope.locationId}|${node.key}`) ? http.GET('/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/sql', { params: { path: { ...path, node_key: node.key } } }) : null, - checkResults(scope).catch(() => []) + checkResults(scope).catch(() => []), + node.kind === 'source' + ? null + : http.GET('/api/control/locations/{location_id}/targets/{target}/nodes/{node_key}/runs', { params: { path: { ...path, node_key: node.key }, query: { limit: 100 } } }), + Promise.all([listedJobs(scope), listedTriggers(scope)]).then(([j, t]) => nodeJobsOf(node.key, j, t)).catch(() => null) ]); - if (status.response.status === 401 || sql?.response.status === 401) onUnauthorized(); + if (status.response.status === 401 || sql?.response.status === 401 || runs?.response.status === 401) onUnauthorized(); if (sql?.response.status === 403) sqlRefused.add(`${scope.locationId}|${node.key}`); // Status and SQL have their own permissions; a refusal leaves that part out rather than failing the page. - return nodeDetailOf(nodes, node, status.data ?? null, sql?.data ?? null, checks.map(toCheck)); + return nodeDetailOf(nodes, node, status.data ?? null, sql?.data ?? null, checks.map(toCheck), { runs: runs?.data ? toNodeRuns(runs.data) : null, jobs }); }, listChecks: async (scope: Scope): Promise => (await checkResults(scope)).map(toCheck), getCheck: async (scope: Scope, id: string): Promise => checkDetailOf(await checkResults(scope, id)), @@ -334,7 +487,27 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt } }; - async function listedJobs(scope: Scope) { + function runPath(scope: Scope, runId: string) { + const { locationId, target } = parseLocationKey(scope.locationId); + return { location_id: locationId, target, run_id: runId }; + } + + /** + * One request per listing at a time: pages that refresh together on a change notice share it + * instead of each asking the server again. A write clears it, so reads after a write see it. + */ + const inFlight = new Map>(); + function shared(key: string, load: () => Promise): Promise { + const pending = inFlight.get(key) as Promise | undefined; + if (pending) return pending; + const started: Promise = load().finally(() => inFlight.get(key) === started && inFlight.delete(key)); + inFlight.set(key, started); + return started; + } + + const listedJobs = (scope: Scope) => shared(`jobs ${scope.locationId}`, () => fetchJobs(scope)); + + async function fetchJobs(scope: Scope) { const { locationId, target } = parseLocationKey(scope.locationId); const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/jobs', { params: { path: { location_id: locationId, target } } }); if (response.status === 401) onUnauthorized(); @@ -342,7 +515,10 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt return data; } - async function listedTriggers(scope: Scope) { + const listedTriggers = (scope: Scope) => shared(`triggers ${scope.locationId}`, () => fetchTriggers(scope)); + + /** Published triggers, each with its newest tick. */ + async function fetchTriggers(scope: Scope) { const { locationId, target } = parseLocationKey(scope.locationId); const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/triggers', { params: { path: { location_id: locationId, target } } }); if (response.status === 401) onUnauthorized(); @@ -360,6 +536,21 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt return data; } + async function roleOverviews(locationKey: string) { + const { locationId, target } = parseLocationKey(locationKey); + const { data, error, response } = await http.GET('/api/admin/locations/{location_id}/targets/{target}/roles', { params: { path: { location_id: locationId, target } } }); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return data; + } + + async function people() { + const { data, error, response } = await http.GET('/api/admin/people'); + if (response.status === 401) onUnauthorized(); + if (!data) throw new ApiError(errorText(error, response), response.status); + return data; + } + /** The newest result of every check, or one check's results newest first. */ async function checkResults(scope: Scope, bindingKey?: string) { const { locationId, target } = parseLocationKey(scope.locationId); @@ -378,8 +569,7 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt /** Triggers with their latest tick and the newest listed run each started. */ async function triggersOf(scope: Scope): Promise { const [wires, runs] = await Promise.all([listedTriggers(scope), recentRuns(scope)]); - const latest = await Promise.all(wires.map(async (t) => (await ticksOf(scope, t.trigger_id, 1)).map(toTick)[0] ?? null)); - return wires.map((t, i) => toTrigger(t, runs, latest[i])); + return wires.map((t) => toTrigger(t, runs, t.latest_tick ? toTick(t.latest_tick) : null)); } async function requireTrigger(scope: Scope, id: string): Promise { @@ -392,12 +582,15 @@ export function createHttpClient({ baseUrl = '', onUnauthorized }: HttpClientOpt async function changeTrigger(triggerId: string, change: Wire['TriggerChange'], reason?: string | null) { const body = { trigger_id: triggerId, event_id: crypto.randomUUID(), actor: '', reason: reason?.trim() || 'Changed from the web UI', change }; const { error, response } = await http.POST('/api/control/triggers', { body, params: { header: { 'x-sqb-csrf': csrfToken(document.cookie) } } }); + inFlight.clear(); if (response.status === 401) onUnauthorized(); if (!response.ok) throw new ApiError(errorText(error, response), response.status); } + const listedRuns = (scope: Scope) => shared(`runs ${scope.locationId}`, () => fetchRuns(scope)); + /** The newest runs of the location, up to the server's cap. */ - async function listedRuns(scope: Scope) { + async function fetchRuns(scope: Scope) { const { locationId, target } = parseLocationKey(scope.locationId); const { data, error, response } = await http.GET('/api/control/locations/{location_id}/targets/{target}/runs', { params: { path: { location_id: locationId, target }, query: { limit: RUN_WINDOW } } diff --git a/ui/src/lib/api/http-locations.test.ts b/ui/src/lib/api/http-locations.test.ts new file mode 100644 index 0000000000..6d07abda32 --- /dev/null +++ b/ui/src/lib/api/http-locations.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from 'vitest'; +import { connectionTestOf, healthOf, warehouseSyncOf } from './http-locations'; + +const now = Date.parse('2026-11-08T12:00:00Z'); +const at = (secondsAgo: number) => new Date(now - secondsAgo * 1000).toISOString(); +type Health = Parameters[0][number]['report']['health']; +const seen = (secondsAgo: number, health: Health, session = 's-1') => ({ + received_at: at(secondsAgo), + report: { event_id: `e-${secondsAgo}`, session_id: session, target: 'prod', health } +}); + +describe('location health', () => { + it('is broken without a check-in in the last 90 seconds, whatever was reported before', () => { + expect(healthOf([seen(91, { kind: 'heartbeat' })], now)).toEqual({ health: 'broken', liveInstances: 0, lastSeenAt: at(91) }); + }); + + it('counts each live instance once', () => { + const observations = [seen(10, { kind: 'heartbeat' }), seen(20, { kind: 'connection', generation: 2, healthy: true }), seen(30, { kind: 'heartbeat' }, 's-2')]; + expect(healthOf(observations, now)).toMatchObject({ health: 'healthy', liveInstances: 2, lastSeenAt: at(10) }); + }); + + it('falls back after a failed load until a newer generation connects', () => { + const failed = seen(20, { kind: 'compile_failed', generation: 3 }); + expect(healthOf([seen(5, { kind: 'heartbeat' }), failed, seen(40, { kind: 'connection', generation: 2, healthy: true })], now).health).toBe('falling_back'); + expect(healthOf([seen(5, { kind: 'connection', generation: 4, healthy: true }), failed], now).health).toBe('healthy'); + }); + + it('needs attention when the latest connection check failed', () => { + const observations = [seen(5, { kind: 'connection', generation: 2, healthy: false }), seen(60, { kind: 'connection', generation: 1, healthy: true })]; + expect(healthOf(observations, now).health).toBe('degraded'); + expect(connectionTestOf(observations)).toMatchObject({ outcome: 'failed', at: at(5) }); + }); +}); + +describe('warehouseSyncOf', () => { + const sync = (at: string, table: 'catalog' | 'statistics', failure: 'read' | 'delivery' | null) => ({ + received_at: at, + report: { event_id: at, session_id: 's1', target: 'development', health: { kind: 'warehouse_sync' as const, generation: 1, location: { schema: 'main' }, table, failure, records_sent: 0, elapsed_millis: 5 } } + }); + + it('is null without any sync report', () => { + expect(warehouseSyncOf([])).toBeNull(); + }); + + it('dates the newest clean sync and names only tables whose newest sync failed', () => { + const result = warehouseSyncOf([ + sync('2026-11-08T12:03:00Z', 'catalog', 'read'), + sync('2026-11-08T12:02:00Z', 'statistics', null), + sync('2026-11-08T12:01:00Z', 'catalog', null), + sync('2026-11-08T12:00:00Z', 'statistics', 'delivery') + ]); + expect(result).toEqual({ lastSyncedAt: '2026-11-08T12:02:00Z', lastError: 'catalog: failed while reading the warehouse' }); + }); + + it('clears the error once the failing table syncs again', () => { + expect(warehouseSyncOf([sync('2026-11-08T12:05:00Z', 'catalog', null), sync('2026-11-08T12:03:00Z', 'catalog', 'read')])?.lastError).toBeNull(); + }); +}); diff --git a/ui/src/lib/api/http-locations.ts b/ui/src/lib/api/http-locations.ts new file mode 100644 index 0000000000..4136768b3e --- /dev/null +++ b/ui/src/lib/api/http-locations.ts @@ -0,0 +1,82 @@ +import type { components } from '$control/schema.d.ts'; +import type { Location, LocationHealth, WarehouseSync } from './types'; +import type { ConnectionTest, LocationSummary } from './types/platform'; + +type Wire = components['schemas']; + +/** Locations report every 30 seconds; the server treats two missed reports as down. */ +const LIVE_WINDOW_MS = 90_000; + +function latestOf(observations: Wire['LocationHealthObservation'][], kind: K) { + return observations.find((o) => o.report.health.kind === kind) as (Wire['LocationHealthObservation'] & { report: { health: Extract } }) | undefined; +} + +/** Health from the newest observations: down without a recent check-in, then load failures, then connection failures. */ +export function healthOf(observations: Wire['LocationHealthObservation'][], now: number): { health: LocationHealth; liveInstances: number; lastSeenAt: string | null } { + const live = new Set(observations.filter((o) => now - Date.parse(o.received_at) <= LIVE_WINDOW_MS).map((o) => o.report.session_id)); + const lastSeenAt = observations.reduce((latest, o) => (!latest || o.received_at > latest ? o.received_at : latest), null); + const failed = latestOf(observations, 'compile_failed'); + const connection = latestOf(observations, 'connection'); + const health: LocationHealth = !live.size + ? 'broken' + : failed && (!connection || failed.report.health.generation >= connection.report.health.generation) + ? 'falling_back' + : connection && !connection.report.health.healthy + ? 'degraded' + : 'healthy'; + return { health, liveInstances: live.size, lastSeenAt }; +} + +/** The connection check a location ran after publishing; the server keeps no message or duration. */ +export function connectionTestOf(observations: Wire['LocationHealthObservation'][]): ConnectionTest | null { + const latest = latestOf(observations, 'connection'); + if (!latest) return null; + const ok = latest.report.health.healthy; + return { outcome: ok ? 'ok' : 'failed', at: latest.received_at, durationMs: 0, trigger: 'publish', by: null, message: ok ? 'Connected after publishing' : 'Could not connect after publishing' }; +} + +/** `observations` is null when the person may not list the whole target. */ +export function toLocationSummary(location: Location, observations: Wire['LocationHealthObservation'][] | null, published: Wire['PublishedSnapshot'] | null, now: number): LocationSummary { + const known = observations ? healthOf(observations, now) : { health: null, liveInstances: 0, lastSeenAt: null }; + return { + id: location.id, + name: location.name, + target: location.target, + ...known, + brokenDeployPolicy: null, + failedDeploy: null, + connectionTest: observations ? connectionTestOf(observations) : null, + activeProblems: 0, + snapshot: published + ? { + id: published.snapshot_id, + generation: published.generation, + gitRevision: published.git_revision ?? '', + gitDirty: published.git_dirty, + gitAuthor: published.git_author ?? '', + publishedAt: published.published_at, + sqbVersion: published.sqb_version + } + : null + }; +} + +const SYNC_STAGE: Record = { + watermark: 'reading where the last sync stopped', + read: 'reading the warehouse', + delivery: 'sending results to the server' +}; + +/** When a table last synced cleanly, and which tables' newest sync failed; null without any sync report. */ +export function warehouseSyncOf(observations: Wire['LocationHealthObservation'][]): WarehouseSync | null { + type Sync = Wire['LocationHealthObservation'] & { report: { health: Extract } }; + const syncs = observations.filter((o): o is Sync => o.report.health.kind === 'warehouse_sync').sort((a, b) => b.received_at.localeCompare(a.received_at)); + if (!syncs.length) return null; + const newest = new Map(); + for (const sync of syncs) if (!newest.has(sync.report.health.table)) newest.set(sync.report.health.table, sync); + const failing = [...newest.values()].flatMap((s) => (s.report.health.failure ? [`${s.report.health.table}: failed while ${SYNC_STAGE[s.report.health.failure]}`] : [])); + return { + lastSyncedAt: syncs.find((s) => !s.report.health.failure)?.received_at ?? null, + lastError: failing.length ? failing.sort().join('\n') : null + }; +} diff --git a/ui/src/lib/api/http-people.test.ts b/ui/src/lib/api/http-people.test.ts new file mode 100644 index 0000000000..518daeb696 --- /dev/null +++ b/ui/src/lib/api/http-people.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from 'vitest'; +import { toAccount, toRoleAssignments } from './http-people'; + +const person = (name: string, groups: string[], has_password = false) => ({ + principal_id: `p-${name}`, + name, + created_at: '2026-11-01T00:00:00Z', + disabled_at: null, + has_password, + groups, + active_sessions: 0, + last_signed_in_at: null +}); + +describe('people', () => { + it('expands a group role to its members only, and scopes it to the location key', () => { + const people = [person('ada', ['data-admins']), person('vic', ['analysts']), person('sam', [], true)]; + const assignments = toRoleAssignments( + { + assignments: [{ assignment_id: 'a-1', principal_id: 'p-sam', name: 'sam', role_key: 'viewer', location_id: 'loc-1', target: 'prod' }], + group_roles: [{ group_name: 'data-admins', role_key: 'admin', location_id: null, target: null }] + }, + people + ); + expect(assignments.map((a) => [a.principal.name, a.role, a.source, a.ssoGroup, a.scope.locationId])).toEqual([ + ['sam', 'viewer', 'manual', null, 'loc-1:prod'], + ['ada', 'admin', 'sso', 'data-admins', null] + ]); + }); + + it('reports a sign-in source only when the server knows one', () => { + expect([toAccount(person('sam', [], true)).source, toAccount(person('ada', ['g'])).source, toAccount(person('vic', [])).source]).toEqual([ + 'password', + 'oidc', + null + ]); + }); +}); diff --git a/ui/src/lib/api/http-people.ts b/ui/src/lib/api/http-people.ts new file mode 100644 index 0000000000..9f3af49421 --- /dev/null +++ b/ui/src/lib/api/http-people.ts @@ -0,0 +1,58 @@ +import type { components } from '$control/schema.d.ts'; +import type { Account, RoleAssignment } from './types/platform'; + +type Wire = components['schemas']; + +/** A person's sign-in source as far as the server knows it: a password, or SSO groups. */ +export function toAccount(wire: Wire['PersonSummary']): Account { + return { + kind: 'user', + name: wire.name, + displayName: wire.name, + email: null, + source: wire.has_password ? 'password' : wire.groups.length ? 'oidc' : null, + ssoGroups: wire.groups, + createdAt: wire.created_at, + lastSeenAt: wire.last_signed_in_at ?? null, + disabled: wire.disabled_at != null, + activeSessions: wire.active_sessions + }; +} + +function scopeKey(locationId: string | null | undefined, target: string | null | undefined): string | null { + return locationId ? (target ? `${locationId}:${target}` : locationId) : null; +} + +/** Assignments given here, then one SSO assignment per member of each mapped group. */ +export function toRoleAssignments(holdings: Wire['RoleHoldings'], people: Wire['PersonSummary'][]): RoleAssignment[] { + const person = (name: string) => ({ kind: 'user' as const, name, displayName: name }); + const manual: RoleAssignment[] = holdings.assignments.map((a) => ({ + id: a.assignment_id, + principal: person(a.name), + role: a.role_key, + scope: { locationId: scopeKey(a.location_id, a.target) }, + source: 'manual', + ssoGroup: null, + definedIn: null, + assignedBy: null, + assignedAt: null, + stale: false + })); + const viaGroups: RoleAssignment[] = holdings.group_roles.flatMap((g) => + people + .filter((p) => p.groups.includes(g.group_name)) + .map((p) => ({ + id: `sso:${g.group_name}:${g.role_key}:${scopeKey(g.location_id, g.target) ?? '*'}:${p.principal_id}`, + principal: person(p.name), + role: g.role_key, + scope: { locationId: scopeKey(g.location_id, g.target) }, + source: 'sso' as const, + ssoGroup: g.group_name, + definedIn: null, + assignedBy: null, + assignedAt: null, + stale: false + })) + ); + return [...manual, ...viaGroups]; +} diff --git a/ui/src/lib/api/http-problems.test.ts b/ui/src/lib/api/http-problems.test.ts new file mode 100644 index 0000000000..9074446a06 --- /dev/null +++ b/ui/src/lib/api/http-problems.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; +import { toPlatformEvents } from './http-problems'; + +const NOW = Date.parse('2026-11-08T12:00:00Z'); +const tick = (name: string, location: string, scheduledFor: string) => ({ + trigger_id: `t-${name}`, + name, + location_id: location, + location_name: location === 'l1' ? 'orders' : 'inventory', + target: 'development', + scheduled_for: scheduledFor +}); + +describe('toPlatformEvents', () => { + it('reports nothing while the scheduler leads and nothing is late', () => { + expect(toPlatformEvents({ scheduler_leader: true, scheduler_lease_expires_at: '2026-11-08T12:00:10Z', broken_locations: [], overdue_ticks: [] }, NOW)).toEqual([]); + }); + + it('dates a stopped scheduler from its lease expiry, and from now when no server has led', () => { + const [stopped] = toPlatformEvents({ scheduler_leader: false, scheduler_lease_expires_at: '2026-11-08T11:50:00Z', broken_locations: [], overdue_ticks: [] }, NOW); + expect(stopped).toMatchObject({ kind: 'scheduler_no_leader', severity: 'critical', locationId: null, occurredAt: '2026-11-08T11:50:00Z' }); + const [never] = toPlatformEvents({ scheduler_leader: false, broken_locations: [], overdue_ticks: [] }, NOW); + expect(never.occurredAt).toBe('2026-11-08T12:00:00.000Z'); + }); + + it('keys broken locations by location and target', () => { + const [broken] = toPlatformEvents( + { scheduler_leader: true, broken_locations: [{ location_id: 'l2', location_name: 'inventory', target: 'development', last_seen_at: '2026-11-08T11:55:00Z' }], overdue_ticks: [] }, + NOW + ); + expect(broken).toMatchObject({ kind: 'location_broken', locationId: 'l2:development', occurredAt: '2026-11-08T11:55:00Z', subject: 'inventory · development' }); + }); + + it('groups late ticks per location, dated and named by the oldest', () => { + const events = toPlatformEvents( + { + scheduler_leader: true, + broken_locations: [], + overdue_ticks: [tick('hourly_orders', 'l1', '2026-11-08T11:40:00Z'), tick('daily_orders', 'l1', '2026-11-08T11:10:00Z'), tick('stock_levels', 'l2', '2026-11-08T11:59:00Z')] + }, + NOW + ); + expect(events.map((e) => [e.locationId, e.occurredAt, e.message])).toEqual([ + ['l1:development', '2026-11-08T11:10:00Z', '2 schedules in orders · development are late, starting with daily_orders.'], + ['l2:development', '2026-11-08T11:59:00Z', 'stock_levels in inventory · development is late.'] + ]); + }); +}); diff --git a/ui/src/lib/api/http-problems.ts b/ui/src/lib/api/http-problems.ts new file mode 100644 index 0000000000..9b1e44dfb2 --- /dev/null +++ b/ui/src/lib/api/http-problems.ts @@ -0,0 +1,56 @@ +import type { components } from '$control/schema.d.ts'; +import { locationKey as keyOf } from './http-client'; +import type { PlatformEvent } from './types'; + +type Wire = components['schemas']; + +const placeOf = (name: string, target: string) => `${name} · ${target}`; + +/** Live problems as active banner events: a stopped scheduler, each broken location, and late schedules per location. */ +export function toPlatformEvents(problems: Wire['PlatformProblems'], now: number): PlatformEvent[] { + const events: PlatformEvent[] = []; + if (!problems.scheduler_leader) { + events.push({ + id: 'scheduler', + kind: 'scheduler_no_leader', + severity: 'critical', + occurredAt: problems.scheduler_lease_expires_at ?? new Date(now).toISOString(), + active: true, + locationId: null, + subject: null, + message: 'No server is running the scheduler, so schedules and sensors aren’t starting runs.' + }); + } + for (const location of problems.broken_locations) { + events.push({ + id: `broken:${keyOf(location.location_id, location.target)}`, + kind: 'location_broken', + severity: 'critical', + occurredAt: location.last_seen_at, + active: true, + locationId: keyOf(location.location_id, location.target), + subject: placeOf(location.location_name, location.target), + message: `${placeOf(location.location_name, location.target)} stopped checking in, so its scheduled runs aren’t starting.` + }); + } + const late = new Map(); + for (const tick of problems.overdue_ticks) { + const key = keyOf(tick.location_id, tick.target); + late.set(key, [...(late.get(key) ?? []), tick]); + } + for (const [key, ticks] of late) { + const oldest = ticks.reduce((a, b) => (a.scheduled_for <= b.scheduled_for ? a : b)); + const place = placeOf(oldest.location_name, oldest.target); + events.push({ + id: `late:${key}`, + kind: 'tick_overdue', + severity: 'warning', + occurredAt: oldest.scheduled_for, + active: true, + locationId: key, + subject: place, + message: ticks.length === 1 ? `${oldest.name} in ${place} is late.` : `${ticks.length} schedules in ${place} are late, starting with ${oldest.name}.` + }); + } + return events; +} diff --git a/ui/src/lib/api/http-roles.test.ts b/ui/src/lib/api/http-roles.test.ts new file mode 100644 index 0000000000..189802e195 --- /dev/null +++ b/ui/src/lib/api/http-roles.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from 'vitest'; +import { roleDetailOf, toRoleDefinition } from './http-roles'; +import type { RoleAssignment } from './types/platform'; + +const support = { + key: 'support', + builtin: false, + description: 'Answer order questions', + grants: [{ nodes: ['tag:orders'], permissions: ['view' as const, 'view.sql' as const], matched: { 'tag:orders': ['customers', 'orders'] } }], + holders: 2 +}; +const assignment = (id: string, role: string, locationId: string | null): RoleAssignment => ({ + id, + principal: { kind: 'user', name: id, displayName: id }, + role, + scope: { locationId }, + source: 'manual', + ssoGroup: null, + definedIn: null, + assignedBy: null, + assignedAt: null, + stale: false +}); + +describe('toRoleDefinition', () => { + it('describes built-in roles itself and keeps a custom role published description', () => { + const viewer = toRoleDefinition({ key: 'viewer', builtin: true, grants: [{ nodes: [], permissions: ['view'], matched: {} }], holders: 0 }); + expect(viewer).toMatchObject({ source: 'built_in', extends: null, recentlyMatched: null }); + expect(viewer.description).toContain('Sees'); + expect(toRoleDefinition(support)).toMatchObject({ source: 'project', description: 'Answer order questions', holders: 2 }); + }); +}); + +describe('roleDetailOf', () => { + it('lists assignments everywhere, on the whole location, or on this target only', () => { + const detail = roleDetailOf( + support, + [ + assignment('everywhere', 'support', null), + assignment('location', 'support', 'l1'), + assignment('target', 'support', 'l1:development'), + assignment('other-target', 'support', 'l1:production'), + assignment('other-location', 'support', 'l2'), + assignment('other-role', 'viewer', null) + ], + 'l1:development' + ); + expect(detail.assignments.map((a) => a.id)).toEqual(['everywhere', 'location', 'target']); + expect(detail.selectors).toEqual([{ selector: 'tag:orders', nodes: ['customers', 'orders'], newSinceLastWeek: [] }]); + expect(JSON.parse(detail.definition ?? '')).toEqual({ description: 'Answer order questions', grants: [{ nodes: ['tag:orders'], permissions: ['view', 'view.sql'] }] }); + }); + + it('has no definition text for built-in roles', () => { + expect(roleDetailOf({ key: 'admin', builtin: true, grants: [], holders: 1 }, [], 'l1:development').definition).toBeNull(); + }); +}); diff --git a/ui/src/lib/api/http-roles.ts b/ui/src/lib/api/http-roles.ts new file mode 100644 index 0000000000..2abbca8945 --- /dev/null +++ b/ui/src/lib/api/http-roles.ts @@ -0,0 +1,45 @@ +import type { components } from '$control/schema.d.ts'; +import type { BuiltInRole, Capability } from './types'; +import type { RoleAssignment, RoleDefinition, RoleDetail } from './types/platform'; + +type Wire = components['schemas']; + +/** What each built-in role is for; the server owns their actions. */ +export const BUILT_IN_DESCRIPTION: Record = { + viewer: 'Sees runs, jobs, triggers, checks and the catalog.', + operator: 'Runs jobs, cancels and reruns runs, and pauses or resumes triggers.', + developer: 'Everything an operator does, plus SQL, ad-hoc runs and connection tests.', + admin: 'Every action, including people, roles and the audit log.' +}; + +/** A role as the Roles list shows it; where it's defined and how it grew aren't known to the server. */ +export function toRoleDefinition(wire: Wire['RoleOverview']): RoleDefinition { + return { + name: wire.key, + source: wire.builtin ? 'built_in' : 'project', + definedIn: null, + description: wire.builtin ? BUILT_IN_DESCRIPTION[wire.key as BuiltInRole] : (wire.description ?? ''), + extends: null, + grants: wire.grants.map((g) => ({ nodes: g.nodes, permissions: g.permissions as Capability[] })), + holders: wire.holders, + recentlyMatched: null + }; +} + +/** Whether an assignment applies to this location key: everywhere, the whole location, or this target. */ +function covers(assignment: RoleAssignment, locationKey: string): boolean { + const scope = assignment.scope.locationId; + return scope === null || scope === locationKey || locationKey.startsWith(`${scope}:`); +} + +/** One role with what each selector matched at the latest publish and who holds it here. */ +export function roleDetailOf(wire: Wire['RoleOverview'], assignments: RoleAssignment[], locationKey: string): RoleDetail { + const role = toRoleDefinition(wire); + return { + ...role, + baseGrants: [], + selectors: wire.grants.flatMap((g) => g.nodes.map((selector) => ({ selector, nodes: g.matched[selector] ?? [], newSinceLastWeek: [] }))), + assignments: assignments.filter((a) => a.role === wire.key && covers(a, locationKey)), + definition: wire.builtin ? null : JSON.stringify({ description: wire.description, grants: wire.grants.map(({ nodes, permissions }) => ({ nodes, permissions })) }, null, 2) + }; +} diff --git a/ui/src/lib/api/http-runs.test.ts b/ui/src/lib/api/http-runs.test.ts index 8e4f328486..a3ffb3fa4b 100644 --- a/ui/src/lib/api/http-runs.test.ts +++ b/ui/src/lib/api/http-runs.test.ts @@ -1,8 +1,9 @@ import { describe, expect, it } from 'vitest'; import type { components } from '$control/schema.d.ts'; -import { runDetailOf, runRowPage, runTabCountsOf, toRunNode, toRuns } from './http-runs'; +import { runDetailOf, runRowPage, runTabCountsOf, toRunNode, toRuns, toRunEvent, toRunStatements, toRunOutput } from './http-runs'; type RunRecord = components['schemas']['RunRecord']; +type W = components['schemas']; const LOCATION = '0191f0aa-orders:development'; const PERSON_ID = '0191f0cc-0000-7000-8000-000000000001'; @@ -122,3 +123,56 @@ describe('run detail', () => { expect(nodes[2].skippedBecauseOf).toEqual({ nodeKey: 'model.n1', runId: 'run', jobKey: 'orders_daily' }); }); }); + +describe('run Recall reads', () => { + const inv = 'worker/c1/execution'; + const event = (type: string, extra: Partial = {}): W['RunEventRecord'] => ({ storage_id: 1, event_id: `e-${type}`, invocation_id: inv, occurred_at: '2026-11-08T06:00:00Z', event_type: type, ...extra }); + + it('maps scheduler facts and sqb events, builds messages from metadata only and drops other kinds', () => { + const mapped = [ + event('run_claimed', { invocation_id: 'scheduler/r1' }), + event('statement_completed', { duration_ms: 1500, affected_rows: 1, resource_id: `["${inv}","model:orders"]`, statement_id: 's1' }), + event('resource_attempt_failed', { severity: 'error', error_type: 'ExecutionFailed', error_code: 'R002', resource_id: `["${inv}","model:orders"]` }), + event('operation_started'), + event('location_health', { invocation_id: 'scheduler/r1' }) + ].flatMap((e) => toRunEvent(e, 'r1') ?? []); + expect(mapped.map((e) => [e.type, e.producer, e.level, e.nodeKey, e.statementId, e.message])).toEqual([ + ['claimed', 'scheduler', 'info', null, null, ''], + ['statement_completed', 'sqb', 'info', 'model:orders', 's1', '1.5 s · 1 row'], + ['resource_attempt_failed', 'sqb', 'error', 'model:orders', null, 'ExecutionFailed: R002'] + ]); + }); + + it('orders statements as sent and tells running from finished by the end time', () => { + const statement = (id: string, extra: Partial): W['RunStatementRecord'] => ({ invocation_id: inv, statement_id: id, sql_available: true, ...extra }); + const statements = toRunStatements( + [ + statement('s1', { resource_id: `["${inv}","seed:countries"]`, statement_kind: 'CREATE', ended_at: '2026-11-08T06:00:01Z', affected_rows: 3 }), + statement('s2', { resource_name: 'orders', outcome: 'failed', error_type: 'CatalogException', sql_available: false }), + statement('s3', { purpose: 'model_build', started_at: '2026-11-08T06:00:02Z' }) + ], + 'r1' + ); + expect(statements.map((s) => [s.sequence, s.nodeKey, s.kind, s.status, s.rowsAffected, s.sqlRecorded, s.error])).toEqual([ + [1, 'seed:countries', 'CREATE', 'completed', 3, true, null], + [2, 'orders', 'statement', 'failed', null, false, 'CatalogException'], + [3, '', 'model_build', 'running', null, true, null] + ]); + }); + + it('merges error text into the output as stderr in time order', () => { + const output = toRunOutput({ + lines: [ + { invocation_id: inv, sequence: 2, stream: 'stdout', occurred_at: '2026-11-08T06:00:03Z', text: 'done' }, + { invocation_id: inv, sequence: 1, stream: 'stderr', occurred_at: '2026-11-08T06:00:01Z', text: 'warning' } + ], + errors: [{ invocation_id: inv, occurred_at: '2026-11-08T06:00:02Z', error_type: 'ValueError', message: 'RuntimeError: bad input' }] + }); + expect(output.map((l) => [l.stream, l.text])).toEqual([ + ['stderr', 'warning'], + ['stderr', 'RuntimeError: bad input'], + ['stdout', 'done'] + ]); + }); +}); + diff --git a/ui/src/lib/api/http-runs.ts b/ui/src/lib/api/http-runs.ts index 86fb68311f..3e91a7100b 100644 --- a/ui/src/lib/api/http-runs.ts +++ b/ui/src/lib/api/http-runs.ts @@ -1,7 +1,8 @@ import type { components } from '$control/schema.d.ts'; import { isActiveRun } from '$lib/status'; -import type { Principal } from './types'; -import type { NodeCounts, Run, RunDetail, RunListQuery, RunNode, RunNodeStatus, RunRow, RunRowPage, RunRowQuery, RunTabCounts } from './types/operate'; +import type { Capability, Principal } from './types'; +import type { AdhocRunCheck, LaunchVerb, NodeCounts, Run, RunDetail, RunListQuery, RunNode, RunNodeStatus, RunRow, RunRowPage, RunRowQuery, RunTabCounts, RunEvent, RunEventLevel, RunOutputLine, RunStatement } from './types/operate'; +import { RECALL_EVENT_TYPES, SCHEDULER_FACTS } from './types/operate'; type Wire = components['schemas']; @@ -10,6 +11,19 @@ export const RUN_WINDOW = 1000; const PERSON = 'principal:'; +/** The server's dry run of an ad-hoc command. */ +export function toAdhocCheck(wire: Wire['AdhocCheck']): AdhocRunCheck { + return { + verb: (wire.verb ?? null) as LaunchVerb | null, + selection: wire.selection, + writeSet: wire.write_set, + settings: wire.settings, + permissions: wire.permissions.map((p) => ({ action: p.action as Capability, allowed: p.allowed, reason: p.reason ?? null })), + error: wire.error ?? null, + launchable: wire.launchable + }; +} + /** Who requested a run: a person by display name and principal ID, otherwise the trigger or system actor. */ export function requesterOf(wire: Wire['RunRecord']): Principal { if (wire.requested_by.startsWith(PERSON)) { @@ -211,3 +225,86 @@ export function runDetailOf(run: Run, wire: Wire['RunRecord'], listed: Run[], no reruns: listed.filter((r) => r.rerunOfRunId === run.id) }; } + +const KNOWN_EVENTS: ReadonlySet = new Set(RECALL_EVENT_TYPES); +/** Scheduler facts are stored as `run_` events in the scheduler invocation. */ +const SCHEDULER_EVENTS: ReadonlySet = new Set(SCHEDULER_FACTS.map((f) => `run_${f}`)); + +/** The node key a Recall resource ID names: `[invocation, key]`, or null for run-level rows. */ +function recallNodeKey(resourceId: string | null | undefined): string | null { + if (!resourceId) return null; + try { + const parsed: unknown = JSON.parse(resourceId); + return Array.isArray(parsed) && typeof parsed[1] === 'string' ? parsed[1] : null; + } catch { + return null; + } +} + +function levelOf(severity: string | null | undefined): RunEventLevel { + if (severity === 'warning' || severity === 'warn') return 'warn'; + if (severity === 'error' || severity === 'critical') return 'error'; + return severity === 'debug' ? 'debug' : 'info'; +} + +function durationText(ms: number | null | undefined): string { + if (ms === null || ms === undefined) return ''; + return ms < 1000 ? `${Math.round(ms)} ms` : `${(ms / 1000).toFixed(1)} s`; +} + +/** + * A run event from its metadata. The server leaves payloads out because they can carry SQL and + * error text, so the message is built from the type, counts, duration and error code only. + * Event kinds the run page does not show (operations, node projections) are dropped. + */ +export function toRunEvent(wire: Wire['RunEventRecord'], runId: string): RunEvent | null { + const scheduler = SCHEDULER_EVENTS.has(wire.event_type); + if (!scheduler && !KNOWN_EVENTS.has(wire.event_type)) return null; + const type = (scheduler ? wire.event_type.slice('run_'.length) : wire.event_type) as RunEvent['type']; + const rows = wire.affected_rows ?? wire.row_count; + const failure = [wire.error_type, wire.error_code].filter(Boolean).join(': '); + const facts = [durationText(wire.duration_ms), rows === null || rows === undefined ? '' : `${rows} ${rows === 1 ? 'row' : 'rows'}`].filter(Boolean).join(' · '); + const message = type === 'requested' ? 'Requested' : type === 'cancel_requested' ? 'Cancel requested' : wire.event_type.endsWith('_failed') ? failure || facts || 'no error code recorded' : type === 'audit_completed' ? (wire.outcome ?? '') : facts; + return { + id: wire.event_id, + storageId: wire.storage_id, + runId, + producer: wire.invocation_id.startsWith('scheduler/') ? 'scheduler' : 'sqb', + type, + occurredAt: wire.occurred_at, + level: levelOf(wire.severity), + invocationId: wire.invocation_id, + nodeKey: recallNodeKey(wire.resource_id), + statementId: wire.statement_id ?? null, + attempt: null, + message + }; +} + +/** Statement metadata in start order; error text is never sent, only its type and code. */ +export function toRunStatements(wire: Wire['RunStatementRecord'][], runId: string): RunStatement[] { + return wire.map((s, i) => ({ + id: s.statement_id, + runId, + invocationId: s.invocation_id, + sequence: i + 1, + nodeKey: recallNodeKey(s.resource_id) ?? s.resource_name ?? '', + kind: s.statement_kind ?? s.purpose ?? 'statement', + status: s.outcome === 'failed' ? 'failed' : s.outcome === 'cancelled' ? 'cancelled' : s.ended_at || s.outcome ? 'completed' : 'running', + startedAt: s.started_at ?? '', + endedAt: s.ended_at ?? null, + rowsAffected: s.affected_rows ?? s.row_count ?? null, + rowsSoFar: null, + queryId: s.query_id ?? null, + sqlRecorded: s.sql_available, + error: [s.error_type, s.error_code].filter(Boolean).join(': ') || null + })); +} + +/** Captured lines and error text in time order; error text reads as stderr of its invocation. */ +export function toRunOutput(wire: Wire['RunOutput']): RunOutputLine[] { + const lines: RunOutputLine[] = wire.lines.map((l) => ({ invocationId: l.invocation_id, stream: l.stream === 'stderr' ? 'stderr' : 'stdout', at: l.occurred_at, text: l.text })); + const errors: RunOutputLine[] = wire.errors.map((e) => ({ invocationId: e.invocation_id, stream: 'stderr', at: e.occurred_at ?? '', text: e.message })); + return [...lines, ...errors].sort((a, b) => a.at.localeCompare(b.at)); +} + diff --git a/ui/src/lib/api/mock/access.ts b/ui/src/lib/api/mock/access.ts index bfd059226a..3ad8df2bb2 100644 --- a/ui/src/lib/api/mock/access.ts +++ b/ui/src/lib/api/mock/access.ts @@ -113,9 +113,11 @@ const PLAIN: Record = { 'run.cancel': 'cancel runs', 'run.rerun': 'rerun runs', 'run.rewrite_history': 'full refresh or reprocess past data', + 'run.prioritise': 'run ahead of the queue', + 'run.warehouse': 'choose the warehouse a run uses', 'trigger.toggle': 'pause and resume schedules and sensors', 'sensor.reset_cursor': 'reset where a sensor is up to', - 'run.adhoc': 'build any selection of nodes', + 'run.adhoc': 'run sqb commands on any selection of nodes', 'connection.test': 'test warehouse connections', 'plan.preview': 'preview build plans', 'location.manage': 'manage locations', diff --git a/ui/src/lib/api/mock/fixtures.ts b/ui/src/lib/api/mock/fixtures.ts index e4eea051b5..b56bb33124 100644 --- a/ui/src/lib/api/mock/fixtures.ts +++ b/ui/src/lib/api/mock/fixtures.ts @@ -82,7 +82,7 @@ export function createFixtures(now: number) { ]; const platformEvents: PlatformEvent[] = [ - { id: 'pe-1', kind: 'location_broken', severity: 'warning', occurredAt: ago(48), active: true, locationId: LOCATION_MARKETING, subject: 'marketing-analytics', message: 'The latest deploy failed to load; still running the previous version.' }, + { id: 'pe-1', kind: 'location_broken', severity: 'warning', occurredAt: ago(48), active: true, locationId: LOCATION_MARKETING, subject: 'marketing-analytics', message: 'marketing-analytics stopped checking in, so its scheduled runs aren’t starting.' }, { id: 'pe-2', kind: 'tick_overdue', severity: 'warning', occurredAt: ago(12), active: true, locationId: LOCATION_SHOP, subject: 'segments_hourly', message: 'The segments_hourly schedule should have started a run 12 minutes ago.' }, { id: 'pe-6', kind: 'warehouse_sync_failed', severity: 'warning', occurredAt: ago(95), active: true, locationId: LOCATION_SHOP_STAGING, subject: '_sqlbuild_janitor_events', message: 'Permission denied on _sqlbuild_janitor_events: the catalog connection’s role can’t use schema ARCHIVE, so cleanup history isn’t up to date.' }, { id: 'pe-3', kind: 'heartbeat', severity: 'info', occurredAt: ago(1), active: false, locationId: null, subject: null, message: 'Server heartbeat.' } diff --git a/ui/src/lib/api/mock/operate-store.ts b/ui/src/lib/api/mock/operate-store.ts index 813a2bc267..7f814a49db 100644 --- a/ui/src/lib/api/mock/operate-store.ts +++ b/ui/src/lib/api/mock/operate-store.ts @@ -2,10 +2,12 @@ // its claims, jobs, triggers and sensors. A small loop advances live runs so // cancels, launches and claims behave like they do on the server. -import { flagSpec } from '$lib/run/command-table'; +import { parseCommand } from '$lib/run/command'; +import { flagSpec, VERBS } from '$lib/run/command-table'; import { ApiError } from '../client'; import type { LocalChangeBus } from '../signals'; import type { + AdhocPermission, AdhocRunCheck, AdhocRunRequest, BuiltInRole, @@ -14,6 +16,7 @@ import type { JobDetail, JobGroupMember, JobLaunchOptions, + LaunchVerb, OverlapOutcome, Principal, QueueEntry, @@ -501,20 +504,21 @@ export class MockOperate { launchAdhocRun(scope: Scope, request: AdhocRunRequest): Promise { const check = this.#check(scope, request); - if (!check.adhoc.allowed) throw new ApiError(`Refused: run.adhoc doesn't cover ${check.adhoc.outOfScope.join(', ') || 'this run'}`, 403); - if (check.rewriteHistory.required && !check.rewriteHistory.allowed) throw new ApiError('Refused: requires run.rewrite_history', 403); - if (!check.ok) throw new ApiError(check.problems[0]?.message ?? 'Invalid request', 422); + if (check.error) throw new ApiError(check.error, 400); + const denied = check.permissions.find((p) => !p.allowed); + if (denied) throw new ApiError(denied.reason ?? `missing ${denied.action} permission for this target`, 403); + const parts = parseCommand(request.command.join(' ')); const rec = this.#create({ id: this.#newId(), origin: 'manual', jobKey: null, by: this.#ctx.user(), - select: request.select, - exclude: request.exclude, - flags: request.flags, - nodes: check.writeSet.length ? check.writeSet : check.resolvedNodes.filter((n) => snapshotNode(n)?.kind !== 'source'), - priority: 0, - verb: request.verb + select: parts.select, + exclude: parts.exclude, + flags: parts.flags, + nodes: check.writeSet.length ? check.writeSet : check.selection.filter((n) => snapshotNode(n)?.kind !== 'source'), + priority: request.priority, + verb: parts.verb }); this.#ctx.changes.emit('runs'); return this.#ctx.reply(this.#view(rec, scope)); @@ -864,7 +868,7 @@ export class MockOperate { priority: number; parent?: string; rerunOf?: string; - verb?: AdhocRunRequest['verb']; + verb?: LaunchVerb; }): RunRecord { const now = new Date().toISOString(); const job = jobDef(spec.jobKey); @@ -887,7 +891,7 @@ export class MockOperate { rerunOfRunId: spec.rerunOf ?? null, requestedBy: spec.by, priority: spec.priority, - rewriteHistory: Object.keys(spec.flags).some((f) => flagSpec(spec.verb ?? 'build', f)?.rewritesHistory), + rewriteHistory: Object.keys(spec.flags).some((f) => flagSpec(spec.verb ?? 'build', f)?.requires === 'run.rewrite_history'), locationId: '', target: '', snapshotId: 'snap-7d2e41a', @@ -926,49 +930,44 @@ export class MockOperate { if (!this.#ctx.can(capability)) throw new ApiError(`Missing permission: ${capability}`, 403); } + /** The server's dry run, simplified: the same shape, verbs and permission rules. */ #check(scope: Scope, request: AdhocRunRequest): AdhocRunCheck { - const problems: AdhocRunCheck['problems'] = []; - const resolution = resolveSelection(request.select, request.exclude); - for (const term of resolution.unknown) problems.push({ code: 'unknown_selector', message: `No node or selector matches ${term}` }); - if (!resolution.nodes.length) problems.push({ code: 'empty_selection', message: 'The selection resolves to no nodes' }); + void scope; + const refused = (error: string): AdhocRunCheck => ({ verb: null, selection: [], writeSet: [], settings: {}, permissions: [], error, launchable: false }); + const verbName = request.command[0] ?? ''; + if (!VERBS.some((v) => v.name === verbName)) return refused(`sqb ${verbName} is not available for ad-hoc runs`); + const parts = parseCommand(request.command.join(' ')); + const unknownFlag = Object.keys(parts.flags).find((f) => !flagSpec(parts.verb, f)); + if (unknownFlag) return refused(`${unknownFlag} is not available for ad-hoc runs`); + const resolution = resolveSelection(parts.select, parts.exclude); + if (resolution.unknown.length) return refused(`no nodes match selector '${resolution.unknown[0]}'`); + if (!resolution.nodes.length) return refused('the selection resolves to no nodes'); const writable = resolution.nodes.filter((n) => snapshotNode(n)?.kind !== 'source'); const writeSet = - request.verb === 'build' - ? writable.filter((n) => !(request.flags['--no-load'] && snapshotNode(n)?.kind === 'loader')) - : request.verb === 'seed' + parts.verb === 'build' + ? writable.filter((n) => !(parts.flags['--no-load'] && snapshotNode(n)?.kind === 'loader')) + : parts.verb === 'seed' ? writable.filter((n) => snapshotNode(n)?.kind === 'seed') - : request.verb === 'load' - ? writable.filter((n) => snapshotNode(n)?.kind === 'loader') - : []; - const reasons: string[] = []; - for (const [flag, value] of Object.entries(request.flags)) { - const spec = flagSpec(request.verb, flag); - if (!spec) { - problems.push({ code: 'unknown_flag', message: `sqb ${request.verb} has no flag ${flag}` }); - continue; - } - if (spec.type !== 'bool' && value === true) problems.push({ code: 'missing_value', message: `${flag} needs a value` }); - if (spec.type === 'int' && value !== true && !/^\d+$/.test(value)) problems.push({ code: 'invalid_value', message: `${flag} must be a whole number` }); - if (spec.type === 'timestamp' && value !== true && Number.isNaN(Date.parse(value))) problems.push({ code: 'invalid_value', message: `${flag} must be a timestamp, e.g. 2026-10-01T00:00:00Z` }); - if (spec.rewritesHistory) reasons.push(flag); - if (flag.includes('cursor') && !writeSet.some((n) => snapshotNode(n)?.cursor)) problems.push({ code: 'no_cursor', message: `${flag}: no selected node has a cursor` }); - } + : []; const scopeTerms = ADHOC_SCOPE[this.#ctx.role()]; const granted = scopeTerms === null ? null : new Set(writeSetOf(scopeTerms.length ? scopeTerms : ['__none__'], [])); - const outOfScope = !this.#ctx.can('run.adhoc') ? writeSet : granted === null ? [] : writeSet.filter((n) => !granted.has(n)); - const allowed = this.#ctx.can('run.adhoc') && outOfScope.length === 0; - const holders = this.#records.filter((r) => HOLDING.has(r.run.status)); - const claimConflicts = writeSet.flatMap((n) => holders.filter((h) => h.script.nodes.includes(n)).map((h) => ({ runId: h.run.id, nodeKey: n }))); - const rewriteAllowed = this.#ctx.can('run.rewrite_history'); - void scope; + const covered = this.#ctx.can('run.adhoc') && (granted === null || resolution.nodes.every((n) => granted.has(n))); + const decide = (action: Capability, allowed: boolean): AdhocPermission => ({ action, allowed, reason: allowed ? null : `missing ${action} permission for this target` }); + const gates = [...new Set(Object.keys(parts.flags).flatMap((f) => flagSpec(parts.verb, f)?.requires ?? []))]; + const permissions = [ + decide('run.adhoc', covered), + ...gates.map((action) => decide(action, this.#ctx.can(action))), + ...(request.priority > 0 ? [decide('run.prioritise', this.#ctx.can('run.prioritise'))] : []) + ]; + const error = parts.verb === 'build' ? null : `workers do not run sqb ${parts.verb} yet; only build can launch`; return { - resolvedNodes: resolution.nodes, + verb: parts.verb, + selection: resolution.nodes, writeSet, - adhoc: { required: 'run.adhoc', allowed, outOfScope }, - rewriteHistory: { required: reasons.length > 0, reasons, allowed: rewriteAllowed }, - claimConflicts, - problems, - ok: problems.length === 0 && allowed && (!reasons.length || rewriteAllowed) + settings: Object.fromEntries(Object.entries(parts.flags).map(([f, v]) => [f.replace(/^--/, '').replaceAll('-', '_'), v])), + permissions, + error, + launchable: error === null && permissions.every((p) => p.allowed) }; } diff --git a/ui/src/lib/api/types.ts b/ui/src/lib/api/types.ts index 2176c599ce..da588447be 100644 --- a/ui/src/lib/api/types.ts +++ b/ui/src/lib/api/types.ts @@ -18,6 +18,8 @@ export const CAPABILITIES = [ 'run.cancel', 'run.rerun', 'run.rewrite_history', + 'run.prioritise', + 'run.warehouse', 'trigger.toggle', 'sensor.reset_cursor', 'run.adhoc', diff --git a/ui/src/lib/api/types/operate.ts b/ui/src/lib/api/types/operate.ts index 5d0a831c31..27fe6998d1 100644 --- a/ui/src/lib/api/types/operate.ts +++ b/ui/src/lib/api/types/operate.ts @@ -448,30 +448,31 @@ export type SnapshotNode = { // ---- ad-hoc runs (the run panel) ---- -export type LaunchVerb = 'build' | 'test' | 'seed' | 'load' | 'audit' | 'freshness'; +export type LaunchVerb = 'build' | 'test' | 'seed' | 'audit' | 'freshness'; +/** A command as argument tokens after `sqb`, never shell text; the server resolves its selectors. */ export type AdhocRunRequest = { - verb: LaunchVerb; - select: string[]; - exclude: string[]; - /** Other flags exactly as typed, e.g. `{ '--full-refresh': true, '--max-microbatches': '4' }`. */ - flags: Record; + command: string[]; + /** Above 0 the run jumps the queue and needs `run.prioritise`. */ + priority: number; }; -export type RunCheckProblem = { code: string; message: string }; +/** One permission the run needs and whether the person holds it on every resolved node. */ +export type AdhocPermission = { action: Capability; allowed: boolean; reason: string | null }; +/** The server's dry run: what launching would run and whether this person may launch it. */ export type AdhocRunCheck = { - resolvedNodes: string[]; - /** Nodes the run would write; empty for read-only verbs. */ + verb: LaunchVerb | null; + /** The exact nodes the worker would run. */ + selection: string[]; + /** Nodes the run would write and reserve; empty for read-only verbs. */ writeSet: string[]; - /** `run.adhoc` must cover every node in the write set; otherwise the whole request is refused. */ - adhoc: { required: Capability; allowed: boolean; outOfScope: string[] }; - rewriteHistory: { required: boolean; reasons: string[]; allowed: boolean }; - /** Active runs holding write claims on nodes in the write set: the run would queue behind them. */ - claimConflicts: { runId: string; nodeKey: string }[]; - problems: RunCheckProblem[]; - /** Launchable as requested. */ - ok: boolean; + /** Accepted flags as the worker receives them, by setting name. */ + settings: Record; + permissions: AdhocPermission[]; + /** Why the command cannot run regardless of permissions, such as a refused flag or a verb workers can't run yet. */ + error: string | null; + launchable: boolean; }; /** Cursor window for a launch, typed from the selected nodes' cursor type. */ diff --git a/ui/src/lib/api/types/platform.ts b/ui/src/lib/api/types/platform.ts index 4c130b04f8..caf3aad99c 100644 --- a/ui/src/lib/api/types/platform.ts +++ b/ui/src/lib/api/types/platform.ts @@ -163,7 +163,11 @@ export type LocationDetail = { connectionTest: ConnectionTest | null; }; -export type LocationSummary = Pick & { +export type LocationSummary = Pick & { + /** Null when the server can't report it to this person. */ + health: LocationHealth | null; + /** Null when the server doesn't report the policy. */ + brokenDeployPolicy: BrokenDeployPolicy | null; /** Unresolved platform events of this location. */ activeProblems: number; liveInstances: number; @@ -296,7 +300,8 @@ export type AccountSource = 'oidc' | 'trusted_proxy' | 'password'; export type Account = Principal & { kind: 'user'; email: string | null; - source: AccountSource; + /** Null when the server can't tell, such as a person with no password who hasn't used SSO. */ + source: AccountSource | null; ssoGroups: string[]; createdAt: Iso; lastSeenAt: Iso | null; @@ -327,8 +332,8 @@ export type RoleDefinition = { grants: RoleGrant[]; /** People holding the role somewhere. */ holders: number; - /** Nodes the role's selectors started matching in the last 7 days. */ - recentlyMatched: number; + /** Nodes the role's selectors started matching in the last 7 days; null when unknown. */ + recentlyMatched: number | null; }; /** What one selector of a role matches in the current snapshot. */ @@ -344,8 +349,8 @@ export type RoleDetail = RoleDefinition & { baseGrants: RoleGrant[]; selectors: SelectorMatch[]; assignments: RoleAssignment[]; - /** The role's definition as written, read-only. */ - definition: string; + /** The role's definition as published, read-only; null for built-in roles. */ + definition: string | null; }; /** `locationId: null` applies everywhere. */ @@ -369,7 +374,7 @@ export type RoleAssignment = { /** For policy and built-in assignments: where they are set, `path:line`. */ definedIn: string | null; assignedBy: Principal | null; - assignedAt: Iso; + assignedAt: Iso | null; /** The role no longer exists in code; the assignment shows but grants nothing. */ stale: boolean; }; @@ -395,7 +400,8 @@ export type AuditEntry = { id: string; at: Iso; actor: Principal; - action: Capability | 'session.login'; + /** A capability, or a recorded event such as `token.issued`; a denial carries the refused capability. */ + action: string; outcome: AuditOutcome; scope: AssignmentScope & { subject: string | null }; /** Role that granted the action, or for denials null. */ diff --git a/ui/src/lib/components/code/sql-block.svelte b/ui/src/lib/components/code/sql-block.svelte index bde0474f48..54a2a1c5e1 100644 --- a/ui/src/lib/components/code/sql-block.svelte +++ b/ui/src/lib/components/code/sql-block.svelte @@ -73,5 +73,8 @@ font-family: var(--font-mono); font-size: 11.5px; line-height: 1.65; + /* Wrap long lines: inside table cells a scrolling block widens its column instead. */ + white-space: pre-wrap; + overflow-wrap: anywhere; } diff --git a/ui/src/lib/components/shell/platform-event-banner.svelte b/ui/src/lib/components/shell/platform-event-banner.svelte index f376d06cbd..d1e7f2c484 100644 --- a/ui/src/lib/components/shell/platform-event-banner.svelte +++ b/ui/src/lib/components/shell/platform-event-banner.svelte @@ -11,6 +11,11 @@ app.platformEvents.filter((e) => e.active && e.severity !== 'info').sort((a, b) => rank(b) - rank(a)) ); const critical = $derived(events.some((e) => e.severity === 'critical')); + /** Location problems link to the location's page if it exists, else the Locations list; server problems only to server status. */ + function linkOf(event: PlatformEvent): { href: string; text: string } | null { + if (event.locationId) return { href: app.client.supports('getLocation') ? problemHref(event) : '/locations', text: 'View location →' }; + return app.client.supports('platformHealth') ? { href: problemHref(event), text: 'View server status →' } : null; + } {#if events.length} @@ -20,11 +25,12 @@ diff --git a/ui/src/lib/platform/access.ts b/ui/src/lib/platform/access.ts index dd2e3fc644..87cd630ed3 100644 --- a/ui/src/lib/platform/access.ts +++ b/ui/src/lib/platform/access.ts @@ -23,11 +23,11 @@ export function roleSummary(role: Pick = { built_in: 'Built-in', server_config: 'Server config', project: 'Policy file' }; +export const roleSourceLabel: Record = { built_in: 'Built-in', server_config: 'Server config', project: 'Project' }; export const assignmentSourceLabel: Record = { manual: 'Manual', sso: 'SSO group', policy: 'Server config', built_in: 'Default for everyone' }; diff --git a/ui/src/lib/platform/labels.ts b/ui/src/lib/platform/labels.ts index 7d29b9b9a9..9fe55a11fb 100644 --- a/ui/src/lib/platform/labels.ts +++ b/ui/src/lib/platform/labels.ts @@ -38,13 +38,13 @@ export function healthWords(location: Pick = { tick_overdue: 'Schedule overdue', scheduler_no_leader: 'Scheduler stopped', - location_broken: 'Deploy failed', + location_broken: 'Location not checking in', launch_failed: 'Couldn’t start runs', connection_test_failed: 'Connection test failed', warehouse_sync_failed: 'Warehouse sync failing', diff --git a/ui/src/lib/platform/permission-catalog.ts b/ui/src/lib/platform/permission-catalog.ts index 8686dd3bef..ccc4f50685 100644 --- a/ui/src/lib/platform/permission-catalog.ts +++ b/ui/src/lib/platform/permission-catalog.ts @@ -24,9 +24,11 @@ export const PERMISSIONS: Record = { description: 'Rebuild tables from scratch or reprocess earlier periods, replacing data already built.', risk: 'changes_data' }, + 'run.prioritise': { label: 'Put runs ahead of the queue', description: 'Start or rerun a run with a priority above 0, so it is claimed before waiting runs.', risk: 'operate' }, + 'run.warehouse': { label: 'Choose a run’s warehouse', description: 'Start an ad-hoc run on another warehouse configured for the target, which can change cost and speed.', risk: 'operate' }, 'trigger.toggle': { label: 'Pause and resume schedules and sensors', description: 'Stop or restart automatic runs.', risk: 'operate' }, 'sensor.reset_cursor': { label: 'Reset where a sensor is up to', description: 'Move a sensor’s position so it looks at events again.', risk: 'operate' }, - 'run.adhoc': { label: 'Build any selection of nodes', description: 'Start an ad-hoc run of nodes outside a declared job, writing to the warehouse.', risk: 'changes_data' }, + 'run.adhoc': { label: 'Run ad-hoc commands', description: 'Run sqb build and other commands on any selection of nodes outside a declared job, writing to the warehouse.', risk: 'changes_data' }, 'connection.test': { label: 'Test warehouse connections', description: 'Run the connection check from a location.', risk: 'operate' }, 'plan.preview': { label: 'Preview build plans', description: 'See what a run would build without running it.', risk: 'read' }, 'location.manage': { label: 'Manage locations and their tokens', description: 'Register locations and create, rotate or revoke their tokens.', risk: 'administer' }, @@ -72,13 +74,32 @@ export function requiresText(action: Capability): string { return `Requires “${PERMISSIONS[action].label}” (${action})`; } -const AUDIT_ONLY: Record, string> = { - 'session.login': 'Signed in' +const AUDIT_ONLY: Record = { + 'session.login': 'Signed in', + 'admin.granted': 'Changed administrators', + 'authorization.denied': 'Denied', + 'cli.login.started': 'Started CLI sign-in', + 'cli.login.decided': 'Decided CLI sign-in', + 'cli.login.exchanged': 'Completed CLI sign-in', + 'evidence.read': 'Read sample rows', + 'job.run_requested': 'Requested a job run', + 'location.created': 'Created location', + 'oidc.flow.created': 'Started SSO sign-in', + 'oidc.authenticated': 'Signed in with SSO', + 'password.changed': 'Changed password', + 'principal.created': 'Added person', + 'role.assigned': 'Changed role', + 'run.cancel_requested': 'Requested cancel', + 'run.rerun_requested': 'Requested rerun', + 'session.created': 'Signed in', + 'session.revoked': 'Signed out', + 'sql.read': 'Read SQL', + 'token.issued': 'Issued token', + 'token.revoked': 'Revoked token', + 'trigger.changed': 'Changed trigger' }; /** Plain name of an audited action, including the audit-only ones. */ -export function auditActionLabel(action: AuditEntry['action']): string { - return action in PERMISSIONS ? PERMISSIONS[action as Capability].label : AUDIT_ONLY[action as keyof typeof AUDIT_ONLY]; +export function auditActionLabel(action: string): string { + return action in PERMISSIONS ? PERMISSIONS[action as Capability].label : (AUDIT_ONLY[action] ?? action); } - -export const AUDIT_ACTIONS: AuditEntry['action'][] = [...CAPABILITIES, 'session.login']; diff --git a/ui/src/lib/run/command-table.generated.json b/ui/src/lib/run/command-table.generated.json index 7186bfc624..7a49b42549 100644 --- a/ui/src/lib/run/command-table.generated.json +++ b/ui/src/lib/run/command-table.generated.json @@ -6,51 +6,13 @@ "description": "Build the selected nodes, running their tests and audits", "flags": [ { - "flag": "--no-sql-analysis", - "aliases": [ - "--no-sql-validation" - ], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "disable SQL syntax, binding, type inference, and semantic validation" - }, - { - "flag": "--no-cache", + "flag": "--selection-diagnostics", "aliases": [], "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, - "description": "Bypass the compile analysis cache for this invocation" - }, - { - "flag": "--defer-to", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Read unselected upstream nodes from another target" - }, - { - "flag": "--defer-clone-from", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Clone unselected upstream nodes from another target" - }, - { - "flag": "--defer-sources-to", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Read sources from another target" + "requires": null, + "description": "Include direct-mode stale-out-of-selection diagnostics." }, { "flag": "--no-python", @@ -58,7 +20,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Skip Python nodes" }, { @@ -67,7 +29,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Skip unit tests during the build" }, { @@ -76,7 +38,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Skip audits during the build" }, { @@ -85,7 +47,7 @@ "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "override the start cursor, typed from the selected models' cursor type" }, { @@ -94,7 +56,7 @@ "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "override the end cursor, typed from the selected models' cursor type" }, { @@ -103,7 +65,7 @@ "type": "timestamp", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "timestamp start cursor override" }, { @@ -112,7 +74,7 @@ "type": "timestamp", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "timestamp end cursor override" }, { @@ -121,7 +83,7 @@ "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "integer start cursor override" }, { @@ -130,7 +92,7 @@ "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "integer end cursor override" }, { @@ -139,43 +101,25 @@ "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Process at most this many microbatches per incremental model" }, - { - "flag": "--load", - "aliases": [], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Run loaders as part of the build" - }, { "flag": "--no-load", "aliases": [], "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Skip loaders" }, - { - "flag": "--reload", - "aliases": [], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "Reload loader sources from the beginning" - }, { "flag": "--fail-fast", "aliases": [], "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Stop at the first failed node" }, { @@ -184,7 +128,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "Rebuild incremental models and snapshots from scratch" }, { @@ -193,7 +137,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": true, + "requires": "run.rewrite_history", "description": "Allow a full refresh of snapshot models" }, { @@ -202,7 +146,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.rewrite_history", "description": "Allow replacing a table with a less durable table type" }, { @@ -211,25 +155,16 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.rewrite_history", "description": "Allow lowering a table retention setting" }, - { - "flag": "--allow-missing-migration-origin", - "aliases": [], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Allow a migration whose origin relation no longer exists" - }, { "flag": "--allow-snapshot-schema-change", "aliases": [], "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.rewrite_history", "description": "Allow a schema change on snapshot models" }, { @@ -238,7 +173,7 @@ "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Nodes to run in parallel within this run" }, { @@ -247,7 +182,7 @@ "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Per-test timeout for unit tests this build runs, such as '30s', or 'disabled'" }, { @@ -258,7 +193,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "More detailed output" }, { @@ -269,8 +204,8 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression, e.g. +fact_orders, tag:finance, group:marts" + "requires": null, + "description": "Selector expression, e.g. +fact_orders, tag:finance, path:models/marts/" }, { "flag": "--exclude", @@ -278,25 +213,16 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, + "requires": null, "description": "Selector expression to leave out" }, - { - "flag": "--vars", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Project variables as a JSON or key=value mapping" - }, { "flag": "--warehouse", "aliases": [], "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.warehouse", "description": "warehouse for this invocation; overrides the target's command-group warehouse and the connection warehouse" } ] @@ -305,24 +231,13 @@ "name": "test", "description": "Run unit and scenario tests for the selection", "flags": [ - { - "flag": "--no-sql-analysis", - "aliases": [ - "--no-sql-validation" - ], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "disable SQL syntax, binding, type inference, and semantic validation" - }, { "flag": "--concurrency", "aliases": [], "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Nodes to run in parallel within this run" }, { @@ -331,7 +246,7 @@ "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Per-test timeout for this run, such as '30s' or '2m', or 'disabled'; overrides TEST() and project settings" }, { @@ -340,61 +255,17 @@ "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Run one named parameter case" }, { - "flag": "--select", - "aliases": [ - "-s" - ], - "type": "selector", - "choices": null, - "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression, e.g. +fact_orders, tag:finance, group:marts" - }, - { - "flag": "--exclude", - "aliases": [], - "type": "selector", - "choices": null, - "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression to leave out" - }, - { - "flag": "--vars", + "flag": "--inspect", "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Project variables as a JSON or key=value mapping" - }, - { - "flag": "--warehouse", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "warehouse for this invocation; overrides the target's command-group warehouse and the connection warehouse" - } - ] - }, - { - "name": "seed", - "description": "Load seed files", - "flags": [ - { - "flag": "--concurrency", - "aliases": [], - "type": "int", + "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, - "description": "Nodes to run in parallel within this run" + "requires": null, + "description": "Show the resolved test plan" }, { "flag": "--select", @@ -404,8 +275,8 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression, e.g. +fact_orders, tag:finance, group:marts" + "requires": null, + "description": "Selector expression, e.g. +fact_orders, tag:finance, path:models/marts/" }, { "flag": "--exclude", @@ -413,49 +284,31 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, + "requires": null, "description": "Selector expression to leave out" }, - { - "flag": "--vars", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Project variables as a JSON or key=value mapping" - }, { "flag": "--warehouse", "aliases": [], "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.warehouse", "description": "warehouse for this invocation; overrides the target's command-group warehouse and the connection warehouse" } ] }, { - "name": "load", - "description": "Run loaders into their source tables", + "name": "seed", + "description": "Load seed files", "flags": [ - { - "flag": "--reload", - "aliases": [], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "Reload loader sources from the beginning" - }, { "flag": "--concurrency", "aliases": [], "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Nodes to run in parallel within this run" }, { @@ -466,8 +319,8 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression, e.g. +fact_orders, tag:finance, group:marts" + "requires": null, + "description": "Selector expression, e.g. +fact_orders, tag:finance, path:models/marts/" }, { "flag": "--exclude", @@ -475,79 +328,16 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, + "requires": null, "description": "Selector expression to leave out" }, - { - "flag": "--start-cursor", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "override the start cursor, typed from the selected models' cursor type" - }, - { - "flag": "--end-cursor", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "override the end cursor, typed from the selected models' cursor type" - }, - { - "flag": "--start-cursor-ts", - "aliases": [], - "type": "timestamp", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "timestamp start cursor override" - }, - { - "flag": "--end-cursor-ts", - "aliases": [], - "type": "timestamp", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "timestamp end cursor override" - }, - { - "flag": "--start-cursor-int", - "aliases": [], - "type": "int", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "integer start cursor override" - }, - { - "flag": "--end-cursor-int", - "aliases": [], - "type": "int", - "choices": null, - "repeatable": false, - "rewritesHistory": true, - "description": "integer end cursor override" - }, - { - "flag": "--vars", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Project variables as a JSON or key=value mapping" - }, { "flag": "--warehouse", "aliases": [], "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.warehouse", "description": "warehouse for this invocation; overrides the target's command-group warehouse and the connection warehouse" } ] @@ -556,33 +346,13 @@ "name": "audit", "description": "Evaluate audits against what is already built", "flags": [ - { - "flag": "--no-sql-analysis", - "aliases": [ - "--no-sql-validation" - ], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "disable SQL syntax, binding, type inference, and semantic validation" - }, - { - "flag": "--defer-to", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Read unselected upstream nodes from another target" - }, { "flag": "--concurrency", "aliases": [], "type": "int", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Nodes to run in parallel within this run" }, { @@ -593,8 +363,8 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression, e.g. +fact_orders, tag:finance, group:marts" + "requires": null, + "description": "Selector expression, e.g. +fact_orders, tag:finance, path:models/marts/" }, { "flag": "--exclude", @@ -602,25 +372,16 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, + "requires": null, "description": "Selector expression to leave out" }, - { - "flag": "--vars", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Project variables as a JSON or key=value mapping" - }, { "flag": "--warehouse", "aliases": [], "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.warehouse", "description": "warehouse for this invocation; overrides the target's command-group warehouse and the connection warehouse" } ] @@ -629,24 +390,13 @@ "name": "freshness", "description": "Check source freshness", "flags": [ - { - "flag": "--no-sql-analysis", - "aliases": [ - "--no-sql-validation" - ], - "type": "bool", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "disable SQL syntax, binding, type inference, and semantic validation" - }, { "flag": "--state", "aliases": [], "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Record freshness results in the state tables" }, { @@ -655,7 +405,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Fail when a freshness check errors" }, { @@ -664,7 +414,7 @@ "type": "bool", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": null, "description": "Fail when a source is stale" }, { @@ -675,8 +425,8 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, - "description": "Selector expression, e.g. +fact_orders, tag:finance, group:marts" + "requires": null, + "description": "Selector expression, e.g. +fact_orders, tag:finance, path:models/marts/" }, { "flag": "--exclude", @@ -684,25 +434,16 @@ "type": "selector", "choices": null, "repeatable": true, - "rewritesHistory": false, + "requires": null, "description": "Selector expression to leave out" }, - { - "flag": "--vars", - "aliases": [], - "type": "string", - "choices": null, - "repeatable": false, - "rewritesHistory": false, - "description": "Project variables as a JSON or key=value mapping" - }, { "flag": "--warehouse", "aliases": [], "type": "string", "choices": null, "repeatable": false, - "rewritesHistory": false, + "requires": "run.warehouse", "description": "warehouse for this invocation; overrides the target's command-group warehouse and the connection warehouse" } ] diff --git a/ui/src/lib/run/command-table.ts b/ui/src/lib/run/command-table.ts index c8266b043f..27bbb8e1a4 100644 --- a/ui/src/lib/run/command-table.ts +++ b/ui/src/lib/run/command-table.ts @@ -1,7 +1,7 @@ -// The launchable verbs and flags, generated from the CLI's command table by -// `npm run gen:command-table`. Never hand-edit the generated JSON. +// The ad-hoc verbs and flags, generated from the CLI's command table and ad-hoc +// allowlist by `npm run gen:command-table`. Never hand-edit the generated JSON. -import type { LaunchVerb } from '$lib/api'; +import type { Capability, LaunchVerb } from '$lib/api'; import table from './command-table.generated.json'; export type FlagType = 'bool' | 'string' | 'int' | 'timestamp' | 'selector'; @@ -12,7 +12,8 @@ export type FlagSpec = { type: FlagType; choices: string[] | null; repeatable: boolean; - rewritesHistory: boolean; + /** The permission beyond run.adhoc that setting this flag needs, as the server decides it. */ + requires: Capability | null; description: string; }; diff --git a/ui/src/lib/run/command.test.ts b/ui/src/lib/run/command.test.ts new file mode 100644 index 0000000000..f52159bd19 --- /dev/null +++ b/ui/src/lib/run/command.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import type { SnapshotNode } from '$lib/api'; +import { commandTokens, suggest, withVerb } from './command'; + +const node = (key: string, kind: SnapshotNode['kind'], path: string, tags: string[] = []): SnapshotNode => ({ key, kind, group: '', tags, path, cursor: null, upstream: [] }); + +const NODES: SnapshotNode[] = [ + node('model:customers', 'table', 'models/marts/customers.sql', ['finance']), + node('seed:countries', 'seed', 'seeds/countries.csv'), + node('source:shop.orders', 'source', 'sources.yml') +]; + +describe('the run panel command line', () => { + it('sends the tokens after sqb exactly as typed, for the server to resolve', () => { + expect(commandTokens(' sqb build customers+ --exclude tag:finance --warehouse WH_S ')).toEqual(['build', 'customers+', '--exclude', 'tag:finance', '--warehouse', 'WH_S']); + }); + + it('suggests selectors in the CLI grammar: bare model names, seed: and source: keys, tags and folders', () => { + const values = (typed: string) => suggest(typed, typed.length, NODES).map((s) => s.value); + expect(values('sqb build --select +cust')).toEqual(['+customers']); + expect(values('sqb build --select countr')).toEqual(['seed:countries']); + expect(values('sqb build --select shop.ord')).toEqual(['source:shop.orders']); + expect(values('sqb build --select fin')).toEqual(['tag:finance']); + expect(values('sqb build --select marts')).toEqual(['path:models/marts']); + }); + + it('keeps the selection and only the flags the new verb accepts when switching verbs', () => { + expect(withVerb('sqb build --select customers --full-refresh --warehouse WH_S', 'test')).toBe('sqb test --select customers --warehouse WH_S '); + }); +}); diff --git a/ui/src/lib/run/command.ts b/ui/src/lib/run/command.ts index 5570861b7a..be044befd1 100644 --- a/ui/src/lib/run/command.ts +++ b/ui/src/lib/run/command.ts @@ -1,14 +1,18 @@ -// Parses the run panel's command line into a launch request, and produces -// autocomplete suggestions from the command table and the snapshot's nodes. -// Pure: no side effects. +// Splits the run panel's command line into the tokens the server checks, and +// produces autocomplete suggestions from the command table and the snapshot's +// nodes. The server resolves selectors and refuses flags; nothing here decides +// whether a command may run. Pure: no side effects. -import type { AdhocRunRequest, LaunchVerb, SnapshotNode } from '$lib/api'; +import type { LaunchVerb, SnapshotNode } from '$lib/api'; import { flagSpec, VERBS, type FlagSpec } from './command-table'; -export type ParsedCommand = { - request: AdhocRunRequest; - /** Problems the panel reports before asking the server. */ - errors: string[]; +/** The command line read for the panel's own controls: verb tabs, flag hints and verb switching. */ +export type CommandParts = { + verb: LaunchVerb; + select: string[]; + exclude: string[]; + /** Other flags exactly as typed, e.g. `{ '--full-refresh': true, '--max-microbatches': '4' }`. */ + flags: Record; }; const SELECTOR_FLAGS = new Set(['--select', '-s', '--exclude']); @@ -17,66 +21,56 @@ export function tokenize(text: string): string[] { return text.trim().split(/\s+/).filter(Boolean); } -export function parseCommand(text: string): ParsedCommand { +/** The argument tokens after `sqb`, exactly as the server receives them. */ +export function commandTokens(text: string): string[] { const tokens = tokenize(text); - const errors: string[] = []; - if (tokens[0] === 'sqb') tokens.shift(); + return tokens[0] === 'sqb' ? tokens.slice(1) : tokens; +} + +export function parseCommand(text: string): CommandParts { + const tokens = commandTokens(text); const verbName = tokens.shift() ?? 'build'; - const verb = (VERBS.find((v) => v.name === verbName)?.name ?? 'build') as LaunchVerb; - if (!VERBS.some((v) => v.name === verbName)) errors.push(`${verbName} can't be launched from the panel; use ${VERBS.map((v) => v.name).join(', ')}`); + const verb = VERBS.find((v) => v.name === verbName)?.name ?? 'build'; const select: string[] = []; const exclude: string[] = []; const flags: Record = {}; - let mode: 'select' | 'exclude' | null = 'select'; + let mode: 'select' | 'exclude' = 'select'; for (let i = 0; i < tokens.length; i++) { const token = tokens[i]; if (token.startsWith('-')) { - mode = null; - if (token === '--target') { - errors.push('The target is set by the location; remove --target'); - i++; - continue; - } if (SELECTOR_FLAGS.has(token)) { mode = token === '--exclude' ? 'exclude' : 'select'; continue; } + mode = 'select'; const spec = flagSpec(verb, token); - if (!spec) { - flags[token] = true; - continue; - } - if (spec.type === 'bool') flags[spec.flag] = true; + const value = tokens[i + 1]; + if (!spec || spec.type === 'bool' || value === undefined || value.startsWith('-')) flags[spec?.flag ?? token] = true; else { - const value = tokens[i + 1]; - if (value === undefined || value.startsWith('--')) flags[spec.flag] = true; - else { - flags[spec.flag] = value; - i++; - } + flags[spec.flag] = value; + i++; } continue; } if (mode === 'exclude') exclude.push(token); - else if (mode === 'select') select.push(token); - else errors.push(`Unexpected ${token}; selectors go after --select or --exclude`); + else select.push(token); } - return { request: { verb, select, exclude, flags }, errors }; + return { verb, select, exclude, flags }; } -export function buildCommand(request: AdhocRunRequest): string { - const parts = ['sqb', request.verb]; - if (request.select.length) parts.push('--select', ...request.select); - if (request.exclude.length) parts.push('--exclude', ...request.exclude); - for (const [flag, value] of Object.entries(request.flags)) parts.push(value === true ? flag : `${flag} ${value}`); - return parts.join(' '); +export function buildCommand(parts: CommandParts): string { + const out = ['sqb', parts.verb]; + if (parts.select.length) out.push('--select', ...parts.select); + if (parts.exclude.length) out.push('--exclude', ...parts.exclude); + for (const [flag, value] of Object.entries(parts.flags)) out.push(value === true ? flag : `${flag} ${value}`); + return out.join(' '); } /** Switches the verb, keeping the selection and the flags the new verb accepts. */ export function withVerb(text: string, verb: LaunchVerb): string { - const { request } = parseCommand(text); - const flags = Object.fromEntries(Object.entries(request.flags).filter(([flag]) => flagSpec(verb, flag))); - return buildCommand({ ...request, verb, flags }) + ' '; + const parts = parseCommand(text); + const flags = Object.fromEntries(Object.entries(parts.flags).filter(([flag]) => flagSpec(verb, flag))); + return buildCommand({ ...parts, verb, flags }) + ' '; } export type Suggestion = { value: string; label: string; hint: string }; @@ -93,29 +87,32 @@ export function tokenAt(text: string, caret: number): TokenContext { return { start, end, text: text.slice(start, caret), previousFlag: lastFlag }; } +/** How `sqb` names a node in a selector: seeds and sources by kind, everything else by bare name. */ +export function selectorOf(key: string): string { + return key.startsWith('seed:') || key.startsWith('source:') ? key : key.slice(key.indexOf(':') + 1); +} + function selectorPool(nodes: SnapshotNode[]): { value: string; hint: string }[] { const unique = (values: string[]) => [...new Set(values)].sort(); return [ ...unique(nodes.flatMap((n) => n.tags)).map((t) => ({ value: `tag:${t}`, hint: 'tag' })), - ...unique(nodes.map((n) => n.group)).map((g) => ({ value: `group:${g}`, hint: 'group' })), - ...unique(nodes.map((n) => n.kind)).map((k) => ({ value: `kind:${k}`, hint: 'kind' })), - ...unique(nodes.map((n) => n.path.replace(/\/[^/]+$/, '/'))).map((p) => ({ value: `path:${p}`, hint: 'path' })), - ...nodes.map((n) => ({ value: n.key, hint: n.kind })) + ...unique(nodes.filter((n) => n.path.includes('/')).map((n) => n.path.replace(/\/[^/]+$/, ''))).map((p) => ({ value: `path:${p}`, hint: 'folder' })), + ...nodes.map((n) => ({ value: selectorOf(n.key), hint: n.kind })) ]; } export function suggest(text: string, caret: number, nodes: SnapshotNode[]): Suggestion[] { - const { request } = parseCommand(text); + const parts = parseCommand(text); const ctx = tokenAt(text, caret); - const verb = VERBS.find((v) => v.name === request.verb)!; + const verb = VERBS.find((v) => v.name === parts.verb)!; const tokens = tokenize(text); if (ctx.text.startsWith('-')) { return verb.flags .filter((f) => f.flag.startsWith(ctx.text) && (f.repeatable || !tokens.includes(f.flag) || ctx.text === f.flag)) .slice(0, 14) - .map((f) => ({ value: f.flag, label: f.flag, hint: `${f.type}${f.rewritesHistory ? ' · needs run.rewrite_history' : ''} · ${f.description}` })); + .map((f) => ({ value: f.flag, label: f.flag, hint: `${f.type}${f.requires ? ` · needs ${f.requires}` : ''} · ${f.description}` })); } - const spec: FlagSpec | undefined = ctx.previousFlag ? flagSpec(request.verb, ctx.previousFlag) : undefined; + const spec: FlagSpec | undefined = ctx.previousFlag ? flagSpec(parts.verb, ctx.previousFlag) : undefined; const prevToken = tokenize(text.slice(0, ctx.start)).at(-1) ?? ''; if (spec && spec.type !== 'selector' && prevToken === ctx.previousFlag) { if (spec.choices) return spec.choices.filter((c) => c.startsWith(ctx.text)).map((c) => ({ value: c, label: c, hint: spec.flag })); diff --git a/ui/src/lib/run/run-panel.svelte b/ui/src/lib/run/run-panel.svelte index b993daca1e..90f3030dd5 100644 --- a/ui/src/lib/run/run-panel.svelte +++ b/ui/src/lib/run/run-panel.svelte @@ -1,17 +1,18 @@ @@ -55,7 +58,7 @@ - {#if e.actor.kind === 'user'}{e.actor.name}{:else}{e.actor.name}{/if} + {#if e.actor.kind === 'user' && linksPeople}{e.actor.name}{:else}{e.actor.name}{/if}
{e.ip}
{auditActionLabel(e.action)} {#if e.evidenceRead} read sample rows{/if} - + {formatAssignmentScope(e.scope, locations)} - {#if e.scope.subject}
{e.scope.subject}
{/if} + {#if e.scope.subject}
{e.scope.subject}
{/if} {#if e.decisionId}
- {e.decisionId} + {e.decisionId} - Check this + {#if checksDecisions}Check this{/if}
{/if} {e.grantingRole ?? '—'} {e.summary} -
+
{#if e.before !== null || e.after !== null}{e.before ?? '∅'} → {e.after ?? '∅'} ·{/if} {e.requestId}
@@ -172,7 +175,21 @@ font-size: 11.5px; } .detail { - max-width: 420px; + max-width: 320px; + } + .where .clip { + max-width: 200px; + } + .decision .clip { + max-width: 110px; + } + .detail .clip { + max-width: 300px; + } + .clip { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .decision { display: flex; diff --git a/ui/src/routes/admin/people/+page.svelte b/ui/src/routes/admin/people/+page.svelte index 91ad7db394..615f799646 100644 --- a/ui/src/routes/admin/people/+page.svelte +++ b/ui/src/routes/admin/people/+page.svelte @@ -25,6 +25,7 @@ const d = people.directory; const allowed = $derived(app.can('user.manage') || app.can('role.assign')); const locations = $derived(app.bootstrap?.locations ?? []); + const opensPeople = $derived(app.client.supports('effectiveAccess')); const sources: AssignmentSource[] = ['manual', 'sso', 'policy', 'built_in']; @@ -46,7 +47,7 @@

Everyone who has signed in, with what they can do in each location. People sign in with single sign-on, or sqb login on the command line. - Open someone to see their effective access and why. + {#if opensPeople}Open someone to see their effective access and why.{/if}

@@ -54,13 +55,13 @@ ({ value: r, label: r }))} all="Any role" /> ({ value: l.id, label: l.name }))} all="All locations" /> ({ value: s, label: assignmentSourceLabel[s] }))} all="Any source" /> - {/if} {/snippet} {#snippet actions()} - + {#if app.client.supports('explainAccess')}{/if} {/snippet}
@@ -77,13 +78,13 @@ {#each people.shown as a (a.name)} {@const risky = d.riskyOf(a.name)} - goto(personHref(a))}> + opensPeople && goto(personHref(a))}>
- e.stopPropagation()}>{a.displayName} -
{a.name} · {a.source === 'oidc' ? 'SSO' : accountSourceLabel[a.source]}
+ {#if opensPeople} e.stopPropagation()}>{a.displayName}{:else}{a.displayName}{/if} +
{a.name} · {a.source === 'oidc' ? 'SSO' : a.source ? accountSourceLabel[a.source] : 'no password'}
@@ -145,4 +146,7 @@ gap: 4px; color: var(--sb-warning) !important; } + tr.static { + cursor: default; + } diff --git a/ui/src/routes/admin/people/[name]/+page.svelte b/ui/src/routes/admin/people/[name]/+page.svelte index 5bebd8f738..037735ebfa 100644 --- a/ui/src/routes/admin/people/[name]/+page.svelte +++ b/ui/src/routes/admin/people/[name]/+page.svelte @@ -126,7 +126,7 @@ tab, select} tabs={[ { value: 'roles', label: 'Roles', count: rs.roles.data?.length }, - { value: 'sso', label: 'SSO mappings', count: rs.sso.data?.length } + ...(app.client.supports('listSsoMappings') ? [{ value: 'sso' as const, label: 'SSO mappings', count: rs.sso.data?.length }] : []) ]} />
- {#if tab === 'roles'} + {#if tab === 'roles' || !app.client.supports('listSsoMappings')} {:else} diff --git a/ui/src/routes/admin/roles/[name]/+page.svelte b/ui/src/routes/admin/roles/[name]/+page.svelte index 3d998169d2..cb34c4354a 100644 --- a/ui/src/routes/admin/roles/[name]/+page.svelte +++ b/ui/src/routes/admin/roles/[name]/+page.svelte @@ -69,7 +69,7 @@ {:else} {#snippet actions()} - + {#if r.extends}{/if} {/snippet} {#if r.extends}

@@ -77,7 +77,7 @@ inherit further than one built-in role.

{:else} -

Starts from nothing; only the additions below apply.

+

Starts from nothing; only the additions below apply, on top of any built-in role a person also holds.

{/if} {#if compare} @@ -160,9 +160,11 @@ {/if} {/if} - - - + {#if r.definition} + + + + {/if}
diff --git a/ui/src/routes/admin/roles/roles-tab.svelte b/ui/src/routes/admin/roles/roles-tab.svelte index 38e0b1a219..cb3e8e993e 100644 --- a/ui/src/routes/admin/roles/roles-tab.svelte +++ b/ui/src/routes/admin/roles/roles-tab.svelte @@ -12,8 +12,8 @@

- Roles in {app.location?.name}. Each custom role is a built-in role plus additions on chosen nodes. Roles are defined in code (the project's policy file or the server - config) and are read-only here. + Roles in {app.location?.name}. Built-in roles act on every node; custom roles add actions on chosen nodes. Custom roles come from the project's roles file and are + read-only here.

diff --git a/ui/src/routes/admin/roles/state.svelte.ts b/ui/src/routes/admin/roles/state.svelte.ts index 098666121a..f7865b5afd 100644 --- a/ui/src/routes/admin/roles/state.svelte.ts +++ b/ui/src/routes/admin/roles/state.svelte.ts @@ -6,7 +6,7 @@ import { createResource } from '$lib/state/resource.svelte'; export function createRolesState() { const can = () => app.can('role.assign'); const roles = createResource((client, scope) => (can() ? client.listRoles(scope.locationId) : Promise.resolve([] as RoleDefinition[]))); - const sso = createResource((client) => (can() ? client.listSsoMappings() : Promise.resolve([] as SsoMapping[]))); + const sso = createResource((client) => (can() && client.supports('listSsoMappings') ? client.listSsoMappings() : Promise.resolve([] as SsoMapping[]))); return { roles, diff --git a/ui/src/routes/catalog/+page.svelte b/ui/src/routes/catalog/+page.svelte index afe9457a83..aa9010bdad 100644 --- a/ui/src/routes/catalog/+page.svelte +++ b/ui/src/routes/catalog/+page.svelte @@ -17,7 +17,7 @@ import { createResource } from '$lib/state/resource.svelte'; import { formatAgo, formatStamp } from '$lib/format/time'; import { nodeOutcomeLabel, nodeOutcomeTone } from '$lib/status'; - import { NODE_KINDS, kindLine, nodeIcon, nodeIconKey, nodeKindPlural, shortRunId } from '$lib/build-area/labels'; + import { NODE_KINDS, kindLine, nodeIcon, nodeIconKey, nodeKindPlural, shortName, shortRunId } from '$lib/build-area/labels'; type StatusFilter = 'stale' | 'changed' | 'behind' | 'failed' | 'warn'; @@ -130,7 +130,7 @@
- {#if linksNodes} e.stopPropagation()}>{n.key}{:else}{n.key}{/if} + {#if linksNodes} e.stopPropagation()}>{shortName(n.key)}{:else}{shortName(n.key)}{/if}
{n.description}
diff --git a/ui/src/routes/locations/+page.svelte b/ui/src/routes/locations/+page.svelte index 548b28e2bd..f7cc7c541e 100644 --- a/ui/src/routes/locations/+page.svelte +++ b/ui/src/routes/locations/+page.svelte @@ -19,14 +19,17 @@ let query = $state(''); const all = $derived(locations.data ?? []); const filtered = $derived(all.filter((l) => !query.trim() || l.name.toLowerCase().includes(query.trim().toLowerCase()))); - const unhealthy = $derived(all.filter((l) => l.health !== 'healthy' || l.activeProblems > 0).length); + const unhealthy = $derived(all.filter((l) => (l.health && l.health !== 'healthy') || l.activeProblems > 0).length); + const opensDetail = $derived(app.client.supports('getLocation')); + const showsServer = $derived(app.client.supports('platformHealth')); + const knowsPolicy = $derived(all.some((l) => l.brokenDeployPolicy)); - +{#if showsServer}{/if} Deployed version - + {#if knowsPolicy}{/if} {#each filtered as l (l.id)} - goto(`/locations/${l.id}`)}> + opensDetail && goto(`/locations/${l.id}`)}> - + {#if knowsPolicy}{/if} {:else} - + {/each}
Warehouse Location processesIf a deploy failsIf a deploy fails
- e.stopPropagation()}>{l.name} + {#if opensDetail} e.stopPropagation()}>{l.name}{:else}{l.name}{/if}
{targetWords(l.target)}
- + {#if l.health} + + {:else} + + {/if} {#if l.activeProblems} {/if} @@ -85,13 +92,19 @@
{l.liveInstances === 1 ? 'Running' : l.liveInstances === 0 ? 'Not running' : `${l.liveInstances} running`}
- + {#if l.lastSeenAt}{/if}
{l.brokenDeployPolicy === 'fallback' ? 'Keep running the previous version' : 'Hold scheduled runs until a deploy works'}{l.brokenDeployPolicy === 'fallback' ? 'Keep running the previous version' : l.brokenDeployPolicy ? 'Hold scheduled runs until a deploy works' : '—'}
+ + diff --git a/ui/src/routes/runs/[id]/run-tabs.svelte b/ui/src/routes/runs/[id]/run-tabs.svelte index 080bcf569c..9e75e03dd7 100644 --- a/ui/src/routes/runs/[id]/run-tabs.svelte +++ b/ui/src/routes/runs/[id]/run-tabs.svelte @@ -19,7 +19,7 @@ const shownOutput = $derived(filtering ? output.filter((l) => view.selected.some((k) => l.text.includes(k))) : output); const grouped = $derived((view.detail?.children.length ?? 0) > 0); - const TABS = $derived(bottomTabs(app.client)); + const TABS = $derived(bottomTabs(app.client, (c) => app.can(c))); $effect(() => { if (TABS.length && !TABS.some((t) => t.value === view.tab)) view.tab = TABS[0].value; }); diff --git a/ui/src/routes/runs/[id]/run-view.svelte b/ui/src/routes/runs/[id]/run-view.svelte index f4c5cf56dd..9c21f6cc3e 100644 --- a/ui/src/routes/runs/[id]/run-view.svelte +++ b/ui/src/routes/runs/[id]/run-view.svelte @@ -25,7 +25,7 @@ let topView = $state<'timeline' | 'graph'>('timeline'); /** The graph needs dependency edges and the bottom pane needs execution reads; servers without them show neither. */ const hasGraph = $derived((detail?.edges.length ?? 0) > 0); - const hasBottom = $derived(bottomTabs(app.client).length > 0); + const hasBottom = $derived(bottomTabs(app.client, (c) => app.can(c)).length > 0); /** The axis starts at the first node start, not at queue time. */ const origin = $derived.by(() => { diff --git a/ui/src/routes/runs/[id]/state.svelte.ts b/ui/src/routes/runs/[id]/state.svelte.ts index 61957933a1..e7b5b4db85 100644 --- a/ui/src/routes/runs/[id]/state.svelte.ts +++ b/ui/src/routes/runs/[id]/state.svelte.ts @@ -15,7 +15,7 @@ export function createRunDetailState(runId: string) { const topics = [`run:${runId}`, 'runs']; const detailRes = createResource((client, scope) => client.getRunDetail(scope, runId), topics); const eventsRes = createResource((client, scope) => (client.supports('listRunEvents') ? client.listRunEvents(scope, runId, { limit: 5000 }) : Promise.resolve({ events: [], nextCursor: null })), topics); - const outputRes = createResource((client, scope) => (client.supports('listRunOutput') ? client.listRunOutput(scope, runId) : Promise.resolve([])), topics); + const outputRes = createResource((client, scope) => (client.supports('listRunOutput') && app.can('view.evidence') ? client.listRunOutput(scope, runId) : Promise.resolve([])), topics); const statementsRes = createResource((client, scope) => (client.supports('listRunStatements') ? client.listRunStatements(scope, runId) : Promise.resolve([])), topics); const detail = $derived(detailRes.data ?? null); diff --git a/ui/src/routes/runs/[id]/utils/banners.ts b/ui/src/routes/runs/[id]/utils/banners.ts index 1575750b09..6265efe441 100644 --- a/ui/src/routes/runs/[id]/utils/banners.ts +++ b/ui/src/routes/runs/[id]/utils/banners.ts @@ -1,4 +1,4 @@ -// Plain banners above the run: lost contact, waiting, and cancel progress. +// Plain banners above the run: lost contact, a failure no node explains, waiting, and cancel progress. import type { CancelProgress, RunDetail } from '$lib/api'; import { formatAgo, formatStamp } from '$lib/format/time'; @@ -53,6 +53,9 @@ export function bannersFor(detail: RunDetail, now: number): Banner[] { detail: run.statusReason ?? undefined }); } + if (run.status === 'failed' && run.statusReason && run.nodeCounts.failed === 0) { + out.push({ key: 'failed', tone: 'error', title: 'The run failed.', body: [{ text: run.statusReason }] }); + } if (live?.state === 'never_started') { out.push({ key: 'never', diff --git a/ui/src/routes/runs/[id]/utils/tabs.ts b/ui/src/routes/runs/[id]/utils/tabs.ts index 7cd5014913..d3ec98f497 100644 --- a/ui/src/routes/runs/[id]/utils/tabs.ts +++ b/ui/src/routes/runs/[id]/utils/tabs.ts @@ -1,14 +1,15 @@ // The run page's bottom tabs that the server can load, in display order. -import type { ApiClient } from '$lib/api'; +import type { ApiClient, Capability } from '$lib/api'; import type { BottomTab } from '../types'; -const TABS: { value: BottomTab; label: string; loads: keyof ApiClient }[] = [ +// Output can quote warehouse data, so it shows only where the person may view evidence. +const TABS: { value: BottomTab; label: string; loads: keyof ApiClient; needs?: Capability }[] = [ { value: 'statements', label: 'Statements', loads: 'listRunStatements' }, { value: 'events', label: 'Events', loads: 'listRunEvents' }, - { value: 'output', label: 'Output', loads: 'listRunOutput' } + { value: 'output', label: 'Output', loads: 'listRunOutput', needs: 'view.evidence' } ]; -export function bottomTabs(client: Pick): { value: BottomTab; label: string }[] { - return TABS.filter((t) => client.supports(t.loads)).map(({ value, label }) => ({ value, label })); +export function bottomTabs(client: Pick, can: (capability: Capability) => boolean): { value: BottomTab; label: string }[] { + return TABS.filter((t) => client.supports(t.loads) && (!t.needs || can(t.needs))).map(({ value, label }) => ({ value, label })); } diff --git a/ui/src/routes/runs/run-list.svelte b/ui/src/routes/runs/run-list.svelte index d5e1d9e39f..65eb4640a9 100644 --- a/ui/src/routes/runs/run-list.svelte +++ b/ui/src/routes/runs/run-list.svelte @@ -65,8 +65,8 @@ - - + + diff --git a/website/src/content/docs/docs/cli/server.mdx b/website/src/content/docs/docs/cli/server.mdx index 37144b9e86..cb5d468256 100644 --- a/website/src/content/docs/docs/cli/server.mdx +++ b/website/src/content/docs/docs/cli/server.mdx @@ -133,6 +133,76 @@ data. Every successful inspection records an audit entry without copying SQL int audit cannot be saved, the request fails without returning SQL. Revocation takes effect on the next request, including sessions created by [`sqb login`](/docs/cli/login/). +## Run ad-hoc commands + +People with `run.adhoc` can run a `sqb` command on the current publication without declaring a +job. Use the web UI's Run panel, or send the command as argument tokens after `sqb`: + +```json +{ + "location_id": "0191f0aa-0000-7000-8000-000000000001", + "target": "development", + "command": ["build", "--select", "+orders", "--full-refresh"], + "priority": 0 +} +``` + +`POST /api/control/runs/adhoc/check` with this body is a dry run. It returns the exact nodes the +command selects (`selection`), the nodes it writes (`write_set`), the options the worker receives +(`settings`), and each permission it needs with your decision (`permissions`). `error` explains a +command that can't run whatever your permissions, and `launchable` is `true` when the launch +would be accepted. A dry run queues nothing and records no audit entry. + +To launch, `POST /api/control/runs/adhoc` with the same fields plus a new `run_id` and a +`reason`. The response is `{"applied": true}`; sending the same `run_id` again returns +`{"applied": false}` and starts nothing. The server resolves selectors against the current +publication, and the worker runs exactly the resolved nodes. The target is always the one in the +request. + +| Verb | Ad-hoc runs | +| --- | --- | +| `build` | Dry run and launch | +| `test`, `seed`, `audit`, `freshness` | Dry run only; workers can't run them yet | +| Any other verb | Refused | + +Selectors use the CLI grammar: names, `seed:`, `source:`, `tag:`, `path:` and `test:`, with `+` +for upstream or downstream nodes. Positional selectors work as on the command line. + +| Flags | Verbs | Needs | +| --- | --- | --- | +| `--select`, `-s`, `--exclude` | All | `run.adhoc` on every selected node | +| `--full-refresh`, `--start-cursor`, `--end-cursor`, `--start-cursor-ts`, `--end-cursor-ts`, `--start-cursor-int`, `--end-cursor-int` | `build` | `run.rewrite_history` | +| `--allow-snapshot-full-refresh`, `--allow-snapshot-schema-change`, `--allow-table-type-downgrade`, `--allow-retention-decrease` | `build` | `run.rewrite_history` | +| `--warehouse` | All | `run.warehouse`, and the warehouse configured for the target on the location | +| `--max-microbatches`, `--no-load`, `--no-tests`, `--no-audits`, `--no-python`, `--fail-fast`, `--selection-diagnostics`, `--verbose` | `build` | `run.adhoc` | +| `--concurrency` | `build`, `test`, `seed`, `audit` | `run.adhoc` | +| `--test-timeout` | `build`, `test` | `run.adhoc` | +| `--case`, `--inspect` | `test` | `run.adhoc` | +| `--state`, `--fail-on-error`, `--fail-on-stale` | `freshness` | `run.adhoc` | + +A priority above 0 also needs `run.prioritise`. Among built-in roles only Admin grants +`run.rewrite_history`, `run.warehouse` and `run.prioritise`. Every other flag is refused with +its reason: + +| Refused flags | Reason | +| --- | --- | +| `--vars` | Variables change the published SQL | +| `--defer-to`, `--defer-sources-to`, `--defer-clone-from` | They read another target | +| `--load`, `--reload` | Source loads are not part of ad-hoc runs | +| `--allow-missing-migration-origin` | It can lose data | +| `--no-sql-analysis`, `--no-cache` | Workers verify the published SQL and manage their own cache | +| `--debug` | It prints executed SQL | +| `--since`, `--explain` | They need the worker's Git checkout | +| `--select-file` | It reads a file on the worker | +| `--target`, `--project-dir` and the `--dbt-*` paths | Workers run the published project for the request's target | +| `--json`, `--json-output`, `--event-output`, `--manifest`, `--no-color` | Workers own the run's output | + +For an ad-hoc run, the worker checks `--warehouse` against the warehouses the location +configures for the target before any statement runs. An unconfigured warehouse fails the run with +the reason `the requested warehouse is not configured for this target on this location; no +warehouse statement ran`, which the run page shows. A job's own `warehouse` setting is not +checked this way. + ## Define project roles Put project roles in `roles.toml` at the project root. `sqb location publish` resolves each @@ -156,7 +226,7 @@ permissions = ["view.evidence"] | Selectors | Same grammar as a job's `select`; an unknown tag or name fails publication | | No matching nodes | Publication succeeds with a warning; the grant gives nothing | | Permissions | Any non-administrative action; `user.manage`, `role.assign` and `audit.view` are rejected | -| Runs | `job.run`, `run.adhoc`, `run.rerun` and `run.rewrite_history` need the permission on every node the run writes | +| Runs | `job.run`, `run.rerun` and `run.rewrite_history` need the permission on every node the run writes; an [ad-hoc command](#run-ad-hoc-commands) needs `run.adhoc` and its flags' permissions on every node it selects; a priority above 0 also needs `run.prioritise` | | Triggers | `trigger.toggle` and `sensor.reset_cursor` need the permission on every node the trigger's job writes | | Listings | The catalog, statistics and run list need `view` on every node of the current publication; partial coverage is denied, not filtered | | Several roles | Grants add up across a person's roles | diff --git a/website/src/content/docs/docs/concepts/rules.mdx b/website/src/content/docs/docs/concepts/rules.mdx index 8f761a9ee0..a1e0eb1845 100644 --- a/website/src/content/docs/docs/concepts/rules.mdx +++ b/website/src/content/docs/docs/concepts/rules.mdx @@ -168,6 +168,26 @@ select = [ `Finding`. +## Dialect support + +SQL Rules, and the built-in Rules that read SQL, such as `SQBRMODEL102` and the SQL-test Rules +`SQBRTEST201` to `SQBRTEST203`, run only where SQLBuild parses the project's dialect. Built-in Rules +that read SQL support these adapters: + +| Adapter | Built-in Rules that read SQL | +|---|---| +| DuckDB, MotherDuck | Supported | +| PostgreSQL | Supported | +| Snowflake | Supported | +| SQLite | Supported | +| BigQuery, ClickHouse, Databricks, SQL Server | Not yet | + +Where SQLBuild does not parse the dialect yet, `sqb compile` and `sqb rules run` show `L007` +warnings that name it: one per file for SQL Rules, and one per model or SQL test listing the +built-in Rules that did not run. `L007` never changes an exit code, including for `sqb plan` and +`sqb build`: it reports a gap in SQLBuild's dialect support, not a problem in your SQL. A syntax +error in a supported dialect still fails the Rules that read it. + ## One definition of project validity `sqb compile` is authoritative. SQLBuild evaluates a project in this order: